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: 97.6% 162 / 0 / 166
Functions: 85.7% 6 / 0 / 7
Branches: 81.3% 226 / 0 / 278

performance/ContactShowcase.cpp
Line Branch Exec Source
1 #include "ContactShowcase.h"
2
3 #include "data/CollectedAddressStore.h"
4 #include "data/ContactStore.h"
5 #include "data/VCardCodec.h"
6
7 #include <QBuffer>
8 #include <QImage>
9 #include <QPainter>
10 #include <QScopeGuard>
11 #include <QSqlQuery>
12 #include <QTimeZone>
13
14 namespace ContactShowcase {
15
16 namespace {
17
18 struct Person {
19 const char *given;
20 const char *family;
21 const char *book; // "kontakte", "arbeit", "system", "familie", "local"
22 const char *org;
23 const char *title;
24 const char *emails; // comma separated, first = work/home by book
25 const char *phones;
26 const char *city;
27 const char *birthday; // "YYYY-MM-DD", "--MM-DD" or ""
28 const char *groups; // comma separated
29 int photo; // 0 = none, 1…15 = generated image
30 };
31
32 // Fictional people; the order is irrelevant (the list sorts them).
33 const Person kPeople[] = {
34 {"Mara", "Klein", "kontakte", "Beispiel GmbH", "Teamleitung Vertrieb", "mara.klein@beispiel.example,mara@klein-privat.example", "+49 170 5550101,+49 30 5550100", "Berlin", "1985-04-12", "Team,Kunden", 1},
35 {"Mara", "Klein", "arbeit", "Beispiel GmbH", "Teamleitung Vertrieb", "mara.klein@beispiel.example", "+49 30 5550100", "Berlin", "", "", 0},
36 {"Jonas", "Becker", "kontakte", "", "", "jonas.becker@example.net", "+49 151 5550177", "Hamburg", "--08-15", "Verein", 2},
37 {"Lena", "Wolf", "kontakte", "Studio Nord", "Grafikdesign", "lena@studio-nord.example", "", "Kiel", "1990-07-03", "", 3},
38 {"Felix", "Schäfer", "kontakte", "", "", "felix.schaefer@example.de,f.schaefer@arbeit.example", "+49 341 5550123", "Leipzig", "", "Familie", 0},
39 {"Linh", "Nguyen", "arbeit", "Studio Nord", "Art Director", "linh@studio-nord.example", "+49 151 5550144", "Kiel", "", "", 4},
40 {"Petra", "Fischer", "kontakte", "", "", "petra.fischer@example.de", "+49 89 5550133", "München", "--11-23", "Familie", 0},
41 {"Leon", "Weber", "arbeit", "Weber & Söhne", "Inhaber", "leon.weber@weber-soehne.example", "+49 211 5550170", "Düsseldorf", "", "", 5},
42 {"Sophie", "Hartmann", "kontakte", "Kanzlei Hartmann & Partner", "Rechtsanwältin", "s.hartmann@kanzlei.example,sophie.hartmann@example.com", "0171 5550142", "München", "1982-02-28", "", 0},
43 {"Tim", "Okafor", "kontakte", "", "", "tim@verein.example", "+44 20 7946 0958", "London", "", "Verein", 6},
44 {"Jürgen", "Müller", "kontakte", "Bäckerei Müller", "Meister", "juergen.mueller@baeckerei.example", "+49 89 5550170", "Augsburg", "", "", 0},
45 {"Anna", "Schmidt", "familie", "", "", "anna.schmidt@example.de", "+49 160 5550133", "Potsdam", "1993-05-09", "Laufgruppe", 7},
46 {"Peter", "Lange", "arbeit", "Lange Logistik", "Disposition", "peter.lange@lange-logistik.example", "+49 221 5550101", "Köln", "", "", 0},
47 {"Carla", "Brandt", "kontakte", "Klinikum Süd", "Oberärztin", "carla.brandt@klinikum.example", "+49 711 5550100,+49 172 5550144", "Stuttgart", "1978-09-30", "", 8},
48 {"Hannah", "Vogel", "familie", "", "", "hannah.vogel@example.org,hannah@vogel.example", "+49 176 5550188", "Dresden", "--03-21", "Elternbeirat", 0},
49 {"Moritz", "von Ahlen", "arbeit", "Universität Beispiel", "Professor", "ahlen@uni.example", "+49 351 5550147", "Dresden", "", "Projekt Nordlicht", 0},
50 {"Aylin", "Demir", "familie", "", "", "aylin.demir@example.com", "+49 157 5550161", "Bremen", "1996-12-01", "Laufgruppe", 9},
51 {"Noah", "Richter", "familie", "", "", "noah.richter@example.com", "", "Bremen", "", "Laufgruppe,Elternbeirat", 0},
52 {"Greta", "Lorenz", "arbeit", "Lorenz Consulting", "Beraterin", "greta.lorenz@example.org", "+49 163 5550155", "Frankfurt", "", "Projekt Nordlicht", 10},
53 {"Matthias", "Krüger", "kontakte", "", "", "matthias.krueger@example.de,mk@example.net", "", "Rostock", "", "Team", 0},
54 {"Ömer", "Yıldız", "kontakte", "Yıldız Bau", "Bauleiter", "oemer@yildiz-bau.example", "+49 30 5550199", "Berlin", "1987-06-17", "Kunden", 0},
55 {"Zoë", "Ångström", "kontakte", "", "", "zoe@example.test", "+46 8 5550 1234", "Stockholm", "", "", 11},
56 {"Ines", "Notiz", "local", "", "", "ines@example.test", "", "", "", "", 0},
57 {"Kenji", "Ito", "kontakte", "Ito Trading", "Sales Manager", "kenji.ito@ito-trading.example", "+81 3 5550 1234", "Tokyo", "", "Kunden", 0},
58 {"Ewa", "Kowalski", "kontakte", "", "", "ewa.kowalski@example.pl", "+48 601 555 012", "Wrocław", "1988-02-14", "", 12},
59 {"Bastian", "Roth", "arbeit", "Beispiel GmbH", "Entwickler", "bastian.roth@beispiel.example", "+49 30 5550112", "Berlin", "", "Projekt Nordlicht", 0},
60 {"Clara", "Neumann", "arbeit", "Beispiel GmbH", "Buchhaltung", "clara.neumann@beispiel.example", "+49 30 5550113", "Berlin", "", "", 0},
61 {"David", "Schwarz", "kontakte", "", "", "david.schwarz@example.de", "", "Bonn", "", "Verein", 0},
62 {"Emma", "Zimmermann", "familie", "", "", "emma.zimmermann@example.de", "+49 152 5550120", "Erfurt", "--10-04", "Elternbeirat", 13},
63 {"Finn", "Hoffmann", "kontakte", "", "", "finn.hoffmann@example.de", "+49 152 5550121", "Kassel", "", "Team", 0},
64 {"Gina", "Braun", "kontakte", "Praxis Braun", "Physiotherapeutin", "termine@praxis-braun.example", "+49 69 5550122", "Frankfurt", "", "", 0},
65 {"Henrik", "Koch", "arbeit", "Koch IT", "Administrator", "henrik.koch@koch-it.example", "+49 40 5550123", "Hamburg", "", "", 0},
66 {"Ida", "Schulz", "familie", "", "", "ida.schulz@example.de", "", "Weimar", "2001-01-01", "", 14},
67 {"Jan", "Meyer", "kontakte", "", "", "jan.meyer@example.de", "+49 170 5550124", "Hannover", "", "Verein", 0},
68 {"Anke", "Berger", "kontakte", "Stadtbibliothek", "", "anke.berger@example.de", "", "Potsdam", "", "", 0},
69 {"Sven", "van der Berg", "arbeit", "Van der Berg BV", "Directeur", "sven@vanderberg.example", "+31 20 5550125", "Amsterdam", "", "", 0},
70 {"Priya", "Sharma", "kontakte", "", "", "priya.sharma@example.in", "+91 22 5550 1260", "Mumbai", "", "", 15},
71 {"Lukas", "Wagner", "familie", "", "", "lukas.wagner@example.de", "+49 171 5550127", "Graz", "--07-30", "", 0},
72 {"Marie", "Dubois", "kontakte", "", "", "marie.dubois@example.fr", "+33 1 5550 1280", "Lyon", "", "", 0},
73 {"Oliver", "Smith", "kontakte", "Smith Ltd", "CEO", "oliver@smith-ltd.example", "+44 161 5550129", "Manchester", "", "Kunden", 0},
74 {"Ronja", "", "familie", "", "", "ronja@example.de", "", "", "", "", 0},
75 {"", "", "kontakte", "Stadtwerke Beispielstadt", "", "service@stadtwerke.example", "+49 800 5550000", "Beispielstadt", "", "", 0},
76 {"", "", "arbeit", "Muster AG", "", "info@muster.example", "+49 30 5550300", "Berlin", "", "", 0},
77 {"Max", "Admin", "system", "", "", "admin@cloud.example", "", "", "", "", 0},
78 {"Nina", "Nutzerin", "system", "", "", "nina@cloud.example", "", "", "", "", 0},
79 {"Otto", "Oberer", "system", "", "", "otto@cloud.example", "", "", "", "", 0},
80 };
81
82 const char *kFirstNames[] = {"Alexander", "Benedikt", "Charlotte", "Dominik", "Elif", "Frieda", "Georg", "Helena",
83 "Ivan", "Johanna", "Karim", "Laura", "Mehmet", "Nora", "Oskar", "Paula",
84 "Quentin", "Rosa", "Samuel", "Tanja", "Ulrich", "Valentina", "Wilhelm", "Yasmin"};
85 const char *kFamilyNames[] = {"Albrecht", "Bauer", "Conrad", "Dietrich", "Engel", "Franke", "Graf", "Haas",
86 "Jäger", "Kaiser", "Ludwig", "Martin", "Nowak", "Otto", "Peters", "Quast",
87 "Reuter", "Sommer", "Thiel", "Ulrich", "Vogt", "Winter", "Ziegler", "Özdemir"};
88 const char *kCities[] = {"Berlin", "Hamburg", "München", "Köln", "Leipzig", "Mainz", "Wien", "Zürich"};
89 const char *kCompanies[] = {"", "", "Beispiel GmbH", "Nordwind KG", "", "Stadtwerke", "", "Kreativbüro Süd"};
90
91 1740 ContactCard cardFor(const Person &person, int index) {
92
1/1
✓ Branch 2 → 3 taken 1740 times.
1740 ContactCard card;
93
1/1
✓ Branch 8 → 9 taken 1740 times.
1740 card.uid = QStringLiteral("showcase-%1").arg(index, 3, 10, QLatin1Char('0'));
94
1/1
✓ Branch 14 → 15 taken 1740 times.
1740 card.name.given = QString::fromUtf8(person.given);
95
1/1
✓ Branch 18 → 19 taken 1740 times.
1740 card.name.family = QString::fromUtf8(person.family);
96
1/1
✓ Branch 22 → 23 taken 1740 times.
1740 card.organization = QString::fromUtf8(person.org);
97
1/1
✓ Branch 26 → 27 taken 1740 times.
1740 card.title = QString::fromUtf8(person.title);
98
6/9
✓ Branch 29 → 30 taken 1740 times.
✓ Branch 31 → 32 taken 1575 times.
✓ Branch 31 → 34 taken 165 times.
✓ Branch 33 → 34 taken 690 times.
✓ Branch 33 → 35 taken 885 times.
✓ Branch 36 → 37 taken 1740 times.
✗ Branch 36 → 38 not taken.
✗ Branch 181 → 182 not taken.
✗ Branch 181 → 183 not taken.
1740 const bool work = QByteArray(person.book) == "arbeit" || !card.organization.isEmpty();
99
2/2
✓ Branch 39 → 40 taken 1740 times.
✓ Branch 43 → 44 taken 1740 times.
1740 const QStringList emails = QString::fromUtf8(person.emails).split(QLatin1Char(','), Qt::SkipEmptyParts);
100
2/2
✓ Branch 68 → 46 taken 765 times.
✓ Branch 68 → 69 taken 1740 times.
2505 for (int i = 0; i < emails.size(); ++i) {
101 765 ContactValue value;
102 765 value.value = emails.at(i);
103
8/14
✓ Branch 49 → 50 taken 330 times.
✓ Branch 49 → 53 taken 360 times.
✓ Branch 58 → 59 taken 765 times.
✓ Branch 58 → 60 taken 765 times.
✓ Branch 60 → 61 taken 435 times.
✓ Branch 60 → 62 taken 330 times.
✓ Branch 62 → 63 taken 330 times.
✓ Branch 62 → 64 taken 435 times.
✗ Branch 194 → 195 not taken.
✗ Branch 194 → 196 not taken.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
✗ Branch 201 → 202 not taken.
✗ Branch 201 → 203 not taken.
2220 value.types = {i == 0 && work ? QStringLiteral("work") : QStringLiteral("home")};
104
1/1
✓ Branch 64 → 65 taken 765 times.
765 card.emails.append(value);
105 765 }
106
2/2
✓ Branch 70 → 71 taken 1740 times.
✓ Branch 74 → 75 taken 1740 times.
1740 const QStringList phones = QString::fromUtf8(person.phones).split(QLatin1Char(','), Qt::SkipEmptyParts);
107
2/2
✓ Branch 103 → 77 taken 1335 times.
✓ Branch 103 → 104 taken 1740 times.
3075 for (int i = 0; i < phones.size(); ++i) {
108 1335 ContactValue value;
109 1335 value.value = phones.at(i);
110
5/9
✓ Branch 82 → 83 taken 1305 times.
✓ Branch 83 → 84 taken 1020 times.
✓ Branch 83 → 87 taken 285 times.
✓ Branch 92 → 93 taken 1335 times.
✓ Branch 92 → 95 taken 1335 times.
✗ Branch 222 → 223 not taken.
✗ Branch 222 → 225 not taken.
✗ Branch 230 → 231 not taken.
✗ Branch 230 → 232 not taken.
4995 value.types = {i == 0 && phones.at(i).contains(QLatin1String(" 1")) ? QStringLiteral("cell")
111
4/6
✓ Branch 95 → 96 taken 315 times.
✓ Branch 95 → 97 taken 1020 times.
✓ Branch 97 → 98 taken 1020 times.
✓ Branch 97 → 99 taken 315 times.
✗ Branch 226 → 227 not taken.
✗ Branch 226 → 228 not taken.
4005 : QStringLiteral("work")};
112
1/1
✓ Branch 99 → 100 taken 1335 times.
1335 card.phones.append(value);
113 1335 }
114
2/2
✓ Branch 104 → 105 taken 1665 times.
✓ Branch 104 → 145 taken 75 times.
1740 if (*person.city) {
115 1665 ContactAddress address;
116
1/1
✓ Branch 109 → 110 taken 1665 times.
3330 address.street = QStringLiteral("Beispielweg %1").arg(index % 40 + 1);
117
1/1
✓ Branch 118 → 119 taken 1665 times.
3330 address.postalCode = QStringLiteral("%1").arg(10000 + index * 731 % 89999);
118
1/1
✓ Branch 124 → 125 taken 1665 times.
1665 address.locality = QString::fromUtf8(person.city);
119
6/12
✓ Branch 136 → 137 taken 1665 times.
✓ Branch 136 → 138 taken 1665 times.
✓ Branch 138 → 139 taken 810 times.
✓ Branch 138 → 140 taken 855 times.
✓ Branch 140 → 141 taken 855 times.
✓ Branch 140 → 142 taken 810 times.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 265 not taken.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 272 not taken.
3330 address.types = {work ? QStringLiteral("work") : QStringLiteral("home")};
120
1/1
✓ Branch 142 → 143 taken 1665 times.
1665 card.addresses.append(address);
121 1665 }
122
2/2
✓ Branch 146 → 147 taken 1740 times.
✓ Branch 147 → 148 taken 1740 times.
1740 card.birthday = PartialDate::fromIso(QString::fromUtf8(person.birthday));
123
2/2
✓ Branch 149 → 150 taken 225 times.
✓ Branch 149 → 160 taken 1515 times.
1740 if (person.photo > 0) {
124 225 card.photo.kind = ContactPhoto::Kind::Inline;
125 225 card.photo.mimeType = QStringLiteral("image/jpeg");
126
1/1
✓ Branch 156 → 157 taken 225 times.
225 card.photo.data = photo(person.photo);
127 }
128 1740 return card;
129 11265 }
130
131 1770 bool putRemote(ContactStore &store, qint64 bookId, const QString &path, const ContactCard &card,
132 const QString &version) {
133 1770 StoredCard stored;
134 1770 stored.bookId = bookId;
135 1770 stored.uid = card.uid;
136
3/3
✓ Branch 9 → 10 taken 1770 times.
✓ Branch 10 → 11 taken 1770 times.
✓ Branch 11 → 12 taken 1770 times.
3540 stored.href = QStringLiteral("https://cloud.example") + path + card.uid + QStringLiteral(".vcf");
137
1/1
✓ Branch 24 → 25 taken 1770 times.
3540 stored.etag = QStringLiteral("\"%1\"").arg(card.uid);
138
1/1
✓ Branch 30 → 31 taken 1770 times.
1770 stored.raw = VCardCodec::create(card, version);
139 1770 stored.baseRaw = stored.raw;
140
3/5
✓ Branch 36 → 37 taken 1770 times.
✗ Branch 36 → 40 not taken.
✓ Branch 37 → 38 taken 1770 times.
✓ Branch 38 → 39 taken 1770 times.
✗ Branch 38 → 40 not taken.
3540 return !stored.raw.isEmpty() && store.putCard(stored) > 0;
141 1770 }
142
143 64 qint64 book(ContactStore &store, const QString &account, const QString &path, const QString &name, bool readOnly,
144 const QStringList &versions = {}) {
145
1/1
✓ Branch 2 → 3 taken 64 times.
64 const qint64 id = store.ensureBook(account, path);
146
3/3
✓ Branch 3 → 4 taken 60 times.
✓ Branch 3 → 5 taken 4 times.
✓ Branch 4 → 6 taken 60 times.
64 const auto existing = id > 0 ? store.book(id) : std::nullopt;
147
2/2
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 9 taken 60 times.
64 if (!existing) return 0; // S106-218: the caller reports the degraded store
148 60 AddressBookInfo info = *existing;
149 60 info.displayName = name;
150 60 info.readOnly = readOnly;
151 60 info.fetched = true;
152 60 info.supportedVersions = versions;
153
4/4
✓ Branch 13 → 14 taken 60 times.
✓ Branch 14 → 15 taken 60 times.
✓ Branch 15 → 16 taken 60 times.
✓ Branch 16 → 17 taken 60 times.
60 info.lastSync = QDateTime(QDate(2026, 10, 4), QTime(9, 0), QTimeZone::utc());
154
1/1
✓ Branch 20 → 21 taken 60 times.
60 store.updateBook(info);
155 60 return id;
156 64 }
157
158 } // namespace
159
160 324 QByteArray photo(int seed) {
161 // Abstract, generated image: two overlapping circles on a gradient.
162
1/1
✓ Branch 2 → 3 taken 324 times.
324 QImage image(128, 128, QImage::Format_RGB32);
163
1/1
✓ Branch 3 → 4 taken 324 times.
324 QPainter painter(&image);
164
1/1
✓ Branch 4 → 5 taken 324 times.
324 painter.setRenderHint(QPainter::Antialiasing);
165
1/1
✓ Branch 5 → 6 taken 324 times.
324 QLinearGradient gradient(0, 0, 128, 128);
166
2/2
✓ Branch 6 → 7 taken 324 times.
✓ Branch 7 → 8 taken 324 times.
324 gradient.setColorAt(0, QColor::fromHsv((seed * 47) % 360, 120, 230));
167
2/2
✓ Branch 8 → 9 taken 324 times.
✓ Branch 9 → 10 taken 324 times.
324 gradient.setColorAt(1, QColor::fromHsv((seed * 47 + 90) % 360, 160, 160));
168
3/3
✓ Branch 10 → 11 taken 324 times.
✓ Branch 11 → 12 taken 324 times.
✓ Branch 12 → 13 taken 324 times.
324 painter.fillRect(image.rect(), gradient);
169
1/1
✓ Branch 14 → 15 taken 324 times.
324 painter.setPen(Qt::NoPen);
170
3/3
✓ Branch 15 → 16 taken 324 times.
✓ Branch 16 → 17 taken 324 times.
✓ Branch 17 → 18 taken 324 times.
324 painter.setBrush(QColor::fromHsv((seed * 83) % 360, 90, 250, 170));
171
1/1
✓ Branch 20 → 21 taken 324 times.
324 painter.drawEllipse(QPointF(40 + seed % 30, 50), 36, 36);
172
3/3
✓ Branch 21 → 22 taken 324 times.
✓ Branch 22 → 23 taken 324 times.
✓ Branch 23 → 24 taken 324 times.
324 painter.setBrush(QColor::fromHsv((seed * 131) % 360, 140, 120, 150));
173
1/1
✓ Branch 26 → 27 taken 324 times.
324 painter.drawEllipse(QPointF(84, 80 - seed % 20), 30, 30);
174
1/1
✓ Branch 27 → 28 taken 324 times.
324 painter.end();
175 324 QByteArray bytes;
176
1/1
✓ Branch 28 → 29 taken 324 times.
324 QBuffer buffer(&bytes);
177
1/1
✓ Branch 30 → 31 taken 324 times.
324 buffer.open(QIODevice::WriteOnly);
178
1/1
✓ Branch 31 → 32 taken 324 times.
324 image.save(&buffer, "JPG", 80);
179 324 return bytes;
180 324 }
181
182 16 bool seed(ContactStore &store, QString *error) {
183 1 const auto fail = [error](const QString &message) {
184
1/2
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
1 if (error)
185 1 *error = message;
186 1 return false;
187 16 };
188 struct Book {
189 qint64 id;
190 QString path;
191 QString version;
192 };
193 const QHash<QByteArray, Book> books{
194
3/3
✓ Branch 4 → 5 taken 16 times.
✓ Branch 5 → 6 taken 16 times.
✓ Branch 6 → 7 taken 16 times.
32 {"kontakte", {book(store, AccountCloud, "/remote.php/dav/addressbooks/users/jens/contacts/", "Kontakte", false),
195 "/remote.php/dav/addressbooks/users/jens/contacts/", "3.0"}},
196
3/3
✓ Branch 12 → 13 taken 16 times.
✓ Branch 13 → 14 taken 16 times.
✓ Branch 14 → 15 taken 16 times.
32 {"arbeit", {book(store, AccountCloud, "/remote.php/dav/addressbooks/users/jens/arbeit/", "Arbeit", false),
197 "/remote.php/dav/addressbooks/users/jens/arbeit/", "3.0"}},
198
3/3
✓ Branch 20 → 21 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
✓ Branch 22 → 23 taken 16 times.
32 {"system", {book(store, AccountCloud, "/remote.php/dav/addressbooks/system/system/system/", "Systemadressbuch", true),
199 "/remote.php/dav/addressbooks/system/system/system/", "3.0"}},
200
5/7
✓ Branch 29 → 30 taken 16 times.
✓ Branch 30 → 31 taken 16 times.
✓ Branch 31 → 32 taken 16 times.
✓ Branch 44 → 45 taken 16 times.
✓ Branch 44 → 46 taken 16 times.
✗ Branch 496 → 497 not taken.
✗ Branch 496 → 498 not taken.
48 {"familie", {book(store, AccountRadicale, "/jens/familie/", "Familie & Freunde", false, {"4.0"}), "/jens/familie/",
201 "4.0"}},
202
2/4
✓ Branch 37 → 38 taken 64 times.
✓ Branch 37 → 39 taken 16 times.
✗ Branch 479 → 480 not taken.
✗ Branch 479 → 481 not taken.
80 };
203
2/2
✓ Branch 75 → 64 taken 61 times.
✓ Branch 75 → 76 taken 15 times.
76 for (const Book &target : books)
204
2/2
✓ Branch 65 → 66 taken 1 time.
✓ Branch 65 → 73 taken 60 times.
62 if (target.id <= 0) return fail(QStringLiteral("Cannot create the showcase address books"));
205 // S106-218: every error return below rolls the unit back instead of
206 // leaving the transaction open on the store.
207
2/3
✓ Branch 76 → 77 taken 15 times.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 85 taken 15 times.
15 if (!store.beginTransaction()) return fail(QStringLiteral("Cannot start the showcase transaction"));
208 15 auto rollback = qScopeGuard([&store] { store.rollbackTransaction(); });
209 15 int index = 0;
210 15 QHash<QByteArray, QStringList> groupMembers; // book:group → member UIDs
211
2/2
✓ Branch 196 → 87 taken 690 times.
✓ Branch 196 → 197 taken 15 times.
705 for (const Person &person : kPeople) {
212
1/1
✓ Branch 87 → 88 taken 690 times.
690 ContactCard card = cardFor(person, index++);
213 // A company without a person is an organisation (S106-218); a vCard 3.0
214 // book cannot store KIND:org, so only 4.0 books keep it.
215
10/16
✓ Branch 88 → 89 taken 690 times.
✓ Branch 90 → 91 taken 30 times.
✓ Branch 90 → 95 taken 660 times.
✓ Branch 91 → 92 taken 30 times.
✓ Branch 93 → 94 taken 30 times.
✗ Branch 93 → 95 not taken.
✓ Branch 96 → 97 taken 30 times.
✓ Branch 96 → 98 taken 660 times.
✓ Branch 98 → 99 taken 690 times.
✗ Branch 98 → 100 not taken.
✓ Branch 100 → 101 taken 30 times.
✓ Branch 100 → 108 taken 660 times.
✗ Branch 570 → 571 not taken.
✗ Branch 570 → 572 not taken.
✗ Branch 574 → 575 not taken.
✗ Branch 574 → 576 not taken.
690 if (QByteArray(person.given).isEmpty() && QByteArray(person.family).isEmpty())
216 30 card.kind = QStringLiteral("org");
217
2/2
✓ Branch 109 → 110 taken 690 times.
✓ Branch 113 → 114 taken 690 times.
690 const QStringList groups = QString::fromUtf8(person.groups).split(QLatin1Char(','), Qt::SkipEmptyParts);
218
1/1
✓ Branch 115 → 116 taken 690 times.
690 const QByteArray bookName(person.book);
219
2/2
✓ Branch 117 → 118 taken 345 times.
✓ Branch 117 → 119 taken 345 times.
690 if (bookName == "kontakte")
220 345 card.categories = groups; // Nextcloud model (D-7)
221
2/2
✓ Branch 132 → 121 taken 330 times.
✓ Branch 132 → 133 taken 690 times.
1020 for (const QString &group : groups)
222
5/5
✓ Branch 122 → 123 taken 330 times.
✓ Branch 123 → 124 taken 330 times.
✓ Branch 124 → 125 taken 330 times.
✓ Branch 125 → 126 taken 330 times.
✓ Branch 126 → 127 taken 330 times.
330 groupMembers[bookName + ':' + group.toUtf8()].append(card.uid);
223
2/2
✓ Branch 135 → 136 taken 15 times.
✓ Branch 135 → 149 taken 675 times.
690 if (card.uid == QLatin1String("showcase-000")) {
224 15 card.note = QStringLiteral(
225 "Kennengelernt auf der Messe 2025.\n\nBevorzugt Termine am Vormittag, nie montags. "
226 "Rückfragen zum Angebot über das Projektpostfach.\n\n— Zweitwohnsitz in Potsdam, "
227 "Assistenz: Clara Neumann.");
228 15 card.nickname = QStringLiteral("Mara");
229 }
230
2/2
✓ Branch 150 → 151 taken 15 times.
✓ Branch 150 → 161 taken 675 times.
690 if (bookName == "local") {
231
2/3
✓ Branch 151 → 152 taken 15 times.
✗ Branch 152 → 153 not taken.
✓ Branch 152 → 160 taken 15 times.
15 if (store.addLocalCard(card) <= 0)
232 ✗ return fail(QStringLiteral("Cannot store a local showcase contact"));
233 15 continue;
234 }
235 675 const Book target = books.value(bookName);
236
2/3
✓ Branch 162 → 163 taken 675 times.
✗ Branch 163 → 164 not taken.
✓ Branch 163 → 174 taken 675 times.
675 if (!putRemote(store, target.id, target.path, card, target.version))
237 ✗ return fail(QStringLiteral("Cannot store showcase contact %1").arg(card.uid));
238 720 }
239 // Generated people fill the list to about 120 entries.
240
2/2
✓ Branch 300 → 198 taken 1050 times.
✓ Branch 300 → 301 taken 15 times.
1065 for (int i = 0; i < 70; ++i) {
241 // Every first/family pair is unique (the second term shifts the family
242 // name once per round of first names).
243 1050 Person person{kFirstNames[i % 24], kFamilyNames[(i * 7 + i / 24) % 24], i % 3 == 0 ? "familie" : "kontakte",
244
4/4
✓ Branch 198 → 199 taken 360 times.
✓ Branch 198 → 200 taken 690 times.
✓ Branch 201 → 202 taken 270 times.
✓ Branch 201 → 203 taken 780 times.
2100 kCompanies[i % 8], "", "", i % 4 == 0 ? "" : "+49 151 5550", kCities[i % 8], "", "", 0};
245
1/1
✓ Branch 204 → 205 taken 1050 times.
1050 ContactCard card = cardFor(person, index++);
246 1050 ContactValue email;
247 2100 email.value = QStringLiteral("%1.%2@example.%3")
248
4/4
✓ Branch 231 → 232 taken 1050 times.
✓ Branch 233 → 234 taken 1050 times.
✓ Branch 234 → 235 taken 1050 times.
✓ Branch 235 → 236 taken 1050 times.
2100 .arg(QString::fromUtf8(person.given).toLower(),
249
3/3
✓ Branch 216 → 217 taken 1050 times.
✓ Branch 217 → 218 taken 1050 times.
✓ Branch 224 → 225 taken 1050 times.
4200 QString::fromUtf8(person.family).toLower().replace(QStringLiteral("ä"), QStringLiteral("ae"))
250 3150 .replace(QStringLiteral("ö"), QStringLiteral("oe")),
251
6/10
✓ Branch 208 → 209 taken 525 times.
✓ Branch 208 → 212 taken 525 times.
✓ Branch 251 → 252 taken 525 times.
✓ Branch 251 → 253 taken 525 times.
✓ Branch 253 → 254 taken 525 times.
✓ Branch 253 → 255 taken 525 times.
✗ Branch 652 → 653 not taken.
✗ Branch 652 → 654 not taken.
✗ Branch 656 → 657 not taken.
✗ Branch 656 → 658 not taken.
4200 i % 2 ? QStringLiteral("de") : QStringLiteral("org"));
252
2/2
✓ Branch 257 → 258 taken 840 times.
✓ Branch 257 → 264 taken 210 times.
1050 if (i % 5 != 0)
253
2/4
✓ Branch 261 → 262 taken 840 times.
✓ Branch 261 → 263 taken 840 times.
✗ Branch 669 → 670 not taken.
✗ Branch 669 → 671 not taken.
1680 card.emails = {email};
254
2/2
✓ Branch 265 → 266 taken 780 times.
✓ Branch 265 → 271 taken 270 times.
1050 if (!card.phones.isEmpty())
255
3/3
✓ Branch 266 → 267 taken 780 times.
✓ Branch 267 → 268 taken 780 times.
✓ Branch 268 → 269 taken 780 times.
780 card.phones.first().value += QString::number(200 + i);
256
1/1
✓ Branch 271 → 272 taken 1050 times.
1050 const Book target = books.value(person.book);
257
2/3
✓ Branch 274 → 275 taken 1050 times.
✗ Branch 275 → 276 not taken.
✓ Branch 275 → 286 taken 1050 times.
1050 if (!putRemote(store, target.id, target.path, card, target.version))
258 ✗ return fail(QStringLiteral("Cannot store generated contact %1").arg(i));
259 1050 }
260 // Group cards: Apple model in "Arbeit", vCard 4 lists in "Familie & Freunde".
261
2/2
✓ Branch 389 → 302 taken 105 times.
✓ Branch 389 → 390 taken 15 times.
120 for (auto it = groupMembers.cbegin(); it != groupMembers.cend(); ++it) {
262
1/1
✓ Branch 305 → 306 taken 105 times.
105 const QByteArray bookName = it.key().left(it.key().indexOf(':'));
263
6/6
✓ Branch 307 → 308 taken 90 times.
✓ Branch 307 → 311 taken 15 times.
✓ Branch 309 → 310 taken 60 times.
✓ Branch 309 → 311 taken 30 times.
✓ Branch 312 → 313 taken 60 times.
✓ Branch 312 → 314 taken 45 times.
105 if (bookName != "arbeit" && bookName != "familie")
264 60 continue;
265
1/1
✓ Branch 314 → 315 taken 45 times.
45 ContactCard group;
266
3/3
✓ Branch 320 → 321 taken 45 times.
✓ Branch 325 → 326 taken 45 times.
✓ Branch 326 → 327 taken 45 times.
90 group.uid = QStringLiteral("showcase-group-%1").arg(QString::fromUtf8(it.key()).replace(QLatin1Char(':'), QLatin1Char('-')));
267
2/2
✓ Branch 335 → 336 taken 45 times.
✓ Branch 336 → 337 taken 45 times.
45 group.formattedName = QString::fromUtf8(it.key().mid(it.key().indexOf(':') + 1));
268 45 group.kind = QStringLiteral("group");
269
2/2
✓ Branch 360 → 349 taken 135 times.
✓ Branch 360 → 361 taken 45 times.
180 for (const QString &uid : it.value())
270
2/2
✓ Branch 353 → 354 taken 135 times.
✓ Branch 354 → 355 taken 135 times.
135 group.members.append(QStringLiteral("urn:uuid:") + uid);
271 45 const Book target = books.value(bookName);
272
2/3
✓ Branch 362 → 363 taken 45 times.
✗ Branch 363 → 364 not taken.
✓ Branch 363 → 371 taken 45 times.
45 if (!putRemote(store, target.id, target.path, group, target.version))
273 ✗ return fail(QStringLiteral("Cannot store a showcase group"));
274 105 }
275
2/3
✓ Branch 390 → 391 taken 15 times.
✗ Branch 391 → 392 not taken.
✓ Branch 391 → 399 taken 15 times.
15 if (!store.commitTransaction()) return fail(QStringLiteral("Cannot commit the showcase contacts"));
276 15 rollback.dismiss();
277 // Collected addresses and usage (frequently contacted, completion ranking).
278
2/2
✓ Branch 400 → 401 taken 15 times.
✓ Branch 401 → 402 taken 15 times.
15 CollectedAddressStore collected(store.database());
279
4/4
✓ Branch 403 → 404 taken 15 times.
✓ Branch 404 → 405 taken 15 times.
✓ Branch 405 → 406 taken 15 times.
✓ Branch 406 → 407 taken 15 times.
15 const QDateTime now(QDate(2026, 10, 4), QTime(9, 0), QTimeZone::utc());
280
2/2
✓ Branch 434 → 409 taken 90 times.
✓ Branch 434 → 435 taken 15 times.
105 for (int round = 0; round < 6; ++round)
281
3/5
✓ Branch 423 → 424 taken 90 times.
✓ Branch 426 → 427 taken 180 times.
✓ Branch 426 → 428 taken 90 times.
✗ Branch 745 → 746 not taken.
✗ Branch 745 → 747 not taken.
360 collected.recordSent({{QStringLiteral("Mara Klein"), QStringLiteral("mara.klein@beispiel.example")},
282 90 {QStringLiteral("Jonas Becker"), QStringLiteral("jonas.becker@example.net")}},
283
1/1
✓ Branch 409 → 410 taken 90 times.
180 true, now.addDays(-round));
284
3/5
✓ Branch 458 → 459 taken 15 times.
✓ Branch 461 → 462 taken 60 times.
✓ Branch 461 → 463 taken 15 times.
✗ Branch 779 → 780 not taken.
✗ Branch 779 → 781 not taken.
90 collected.recordSent({{QStringLiteral("Paketdienst"), QStringLiteral("versand@paket.example")},
285 15 {QString(), QStringLiteral("rechnung@shop.example")},
286 15 {QStringLiteral("Hausverwaltung Lindner"), QStringLiteral("info@hv-lindner.example")},
287 15 {QStringLiteral("Lena Wolf"), QStringLiteral("lena@studio-nord.example")}},
288
1/1
✓ Branch 435 → 436 taken 15 times.
30 true, now.addDays(-3));
289 15 return true;
290 1586 }
291
292 } // namespace ContactShowcase
293