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.4% 301 / 0 / 309
Functions: 100.0% 14 / 0 / 14
Branches: 83.2% 386 / 0 / 464

presentation/ContactPeopleView.cpp
Line Branch Exec Source
1 #include "ContactPeopleView.h"
2
3 #include "ContactEditorFields.h"
4 #include "MetSentence.h"
5 #include "data/ContactIndex.h"
6 #include "data/RelationIndex.h"
7 #include "data/RelationInference.h"
8 #include "data/RelationVocabulary.h"
9 #include "data/ServiceCatalog.h"
10
11 #include <QCoreApplication>
12 #include <QDateTime>
13 #include <QLocale>
14 #include <QSqlQuery>
15 #include <QTimeZone>
16 #include <QVariantList>
17 #include <algorithm>
18
19 namespace ContactPeopleView {
20
21 namespace {
22
23 1297 QString tr(const char *text) { return QCoreApplication::translate("ContactPeopleView", text); }
24
25 struct Other {
26 QString initials;
27 int color = 0;
28 bool hasPhoto = false;
29 };
30
31 2065 Other otherCard(const QSqlDatabase &db, qint64 cardId) {
32 2065 Other other;
33
1/1
✓ Branch 2 → 3 taken 2065 times.
2065 QSqlQuery q(db);
34
1/1
✓ Branch 6 → 7 taken 2065 times.
2065 q.prepare(QStringLiteral("SELECT uid, display_name, has_photo FROM cards WHERE id = ?"));
35
1/1
✓ Branch 11 → 12 taken 2065 times.
2065 q.addBindValue(cardId);
36
5/8
✓ Branch 13 → 14 taken 2065 times.
✓ Branch 14 → 15 taken 2065 times.
✗ Branch 14 → 18 not taken.
✓ Branch 15 → 16 taken 2065 times.
✓ Branch 16 → 17 taken 2065 times.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 2065 times.
✗ Branch 19 → 39 not taken.
2065 if (q.exec() && q.next()) {
37
1/1
✓ Branch 20 → 21 taken 2065 times.
2065 ContactCard named;
38
2/2
✓ Branch 21 → 22 taken 2065 times.
✓ Branch 22 → 23 taken 2065 times.
2065 named.formattedName = q.value(1).toString();
39
1/1
✓ Branch 26 → 27 taken 2065 times.
2065 other.initials = ContactIndex::initials(named);
40
3/3
✓ Branch 29 → 30 taken 2065 times.
✓ Branch 30 → 31 taken 2065 times.
✓ Branch 31 → 32 taken 2065 times.
2065 other.color = ContactIndex::avatarColorIndex(q.value(0).toString());
41
2/2
✓ Branch 34 → 35 taken 2065 times.
✓ Branch 35 → 36 taken 2065 times.
2065 other.hasPhoto = q.value(2).toBool();
42 2065 }
43 2065 return other;
44 2065 }
45
46 // F103-31: who a card id is, for the relationships that follow from others.
47 struct Person {
48 QString key;
49 QString name;
50 QString gender;
51 Other look;
52 };
53
54 1256 Person personOf(const QSqlDatabase &db, qint64 cardId) {
55 1256 Person person;
56
1/1
✓ Branch 2 → 3 taken 1256 times.
1256 QSqlQuery q(db);
57
1/1
✓ Branch 6 → 7 taken 1256 times.
1256 q.prepare(QStringLiteral("SELECT c.book_id, c.uid, c.display_name, COALESCE(t.gender, '') FROM cards c"
58 " LEFT JOIN card_traits t ON t.card_id = c.id WHERE c.id = ?"));
59
1/1
✓ Branch 11 → 12 taken 1256 times.
1256 q.addBindValue(cardId);
60
5/8
✓ Branch 13 → 14 taken 1256 times.
✓ Branch 14 → 15 taken 1256 times.
✗ Branch 14 → 18 not taken.
✓ Branch 15 → 16 taken 1256 times.
✓ Branch 16 → 17 taken 1256 times.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 1256 times.
✗ Branch 19 → 47 not taken.
1256 if (q.exec() && q.next()) {
61
7/7
✓ Branch 20 → 21 taken 1256 times.
✓ Branch 21 → 22 taken 1256 times.
✓ Branch 24 → 25 taken 1256 times.
✓ Branch 25 → 26 taken 1256 times.
✓ Branch 26 → 27 taken 1256 times.
✓ Branch 27 → 28 taken 1256 times.
✓ Branch 28 → 29 taken 1256 times.
1256 person.key = QString::number(q.value(0).toLongLong()) + QLatin1Char('/') + q.value(1).toString();
62
2/2
✓ Branch 36 → 37 taken 1256 times.
✓ Branch 37 → 38 taken 1256 times.
1256 person.name = q.value(2).toString();
63
2/2
✓ Branch 41 → 42 taken 1256 times.
✓ Branch 42 → 43 taken 1256 times.
1256 person.gender = q.value(3).toString();
64 }
65
1/1
✓ Branch 47 → 48 taken 1256 times.
1256 person.look = otherCard(db, cardId);
66 1256 return person;
67 1256 }
68
69 // The relationships of `cardId` that follow from stored ones (never stored
70 // themselves): rows with role "inferred", read-only, with what they follow from.
71 750 QList<QVariantMap> inferredRows(const QSqlDatabase &db, qint64 cardId, const QString &displayName,
72 const QString &holderGender, const QString &meKey) {
73 750 QList<RelationInference::Fact> facts;
74 750 bool mine = false;
75
5/5
✓ Branch 2 → 3 taken 750 times.
✓ Branch 3 → 4 taken 750 times.
✓ Branch 4 → 5 taken 750 times.
✓ Branch 26 → 6 taken 16036 times.
✓ Branch 26 → 27 taken 750 times.
16786 for (const RelationIndex::Edge &edge : RelationIndex::allEdges(db)) {
76
6/6
✓ Branch 9 → 10 taken 14340 times.
✓ Branch 9 → 11 taken 1696 times.
✓ Branch 10 → 11 taken 325 times.
✓ Branch 10 → 12 taken 14015 times.
✓ Branch 13 → 14 taken 2021 times.
✓ Branch 13 → 15 taken 14015 times.
16036 if (edge.role != QLatin1String("relation") || edge.toCardId <= 0)
77 2021 continue;
78
1/1
✓ Branch 16 → 17 taken 14015 times.
14015 facts.append({edge.fromCardId, edge.toCardId, edge.type});
79
6/6
✓ Branch 18 → 19 taken 9001 times.
✓ Branch 18 → 21 taken 5014 times.
✓ Branch 19 → 20 taken 8913 times.
✓ Branch 19 → 21 taken 88 times.
✓ Branch 20 → 21 taken 156 times.
✓ Branch 20 → 22 taken 8757 times.
14015 mine = mine || edge.fromCardId == cardId || edge.toCardId == cardId;
80 750 }
81 750 QList<QVariantMap> rows;
82
2/2
✓ Branch 28 → 29 taken 506 times.
✓ Branch 28 → 30 taken 244 times.
750 if (!mine)
83 506 return rows;
84
1/1
✓ Branch 30 → 31 taken 244 times.
244 const QString holderKey = personOf(db, cardId).key;
85
5/5
✓ Branch 33 → 34 taken 244 times.
✓ Branch 34 → 35 taken 244 times.
✓ Branch 35 → 36 taken 244 times.
✓ Branch 211 → 37 taken 9412 times.
✓ Branch 211 → 212 taken 244 times.
9656 for (const RelationInference::Inferred &found : RelationInference::infer(facts)) {
86
2/2
✓ Branch 38 → 39 taken 8906 times.
✓ Branch 38 → 40 taken 506 times.
9412 if (found.holder != cardId)
87 8906 continue;
88
1/1
✓ Branch 40 → 41 taken 506 times.
506 const Person other = personOf(db, found.other);
89
1/1
✓ Branch 41 → 42 taken 506 times.
506 const Person via = personOf(db, found.via);
90
3/4
✓ Branch 43 → 44 taken 38 times.
✓ Branch 43 → 47 taken 468 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 38 times.
506 const bool isMe = !meKey.isEmpty() && other.key == meKey;
91 //: F103-31 tooltip of an inferred relationship: "Follows from: Rui is Ana's father, and Marta is Rui's daughter".
92
1/1
✓ Branch 48 → 49 taken 506 times.
506 const QString because = tr("Follows from: %1, and %2")
93
1/1
✓ Branch 52 → 53 taken 506 times.
1012 .arg(RelationVocabulary::sentence(via.name, found.firstType, displayName, via.gender),
94
2/2
✓ Branch 50 → 51 taken 506 times.
✓ Branch 53 → 54 taken 506 times.
1518 RelationVocabulary::sentence(other.name, found.secondType, via.name, other.gender));
95
3/5
✓ Branch 151 → 152 taken 506 times.
✓ Branch 154 → 155 taken 9614 times.
✓ Branch 154 → 156 taken 506 times.
✗ Branch 242 → 243 not taken.
✗ Branch 242 → 244 not taken.
11638 rows.append({{QStringLiteral("role"), QStringLiteral("inferred")},
96 1012 {QStringLiteral("type"), found.type},
97 1518 {QStringLiteral("category"), QStringLiteral("family")},
98
1/1
✓ Branch 81 → 82 taken 506 times.
1012 {QStringLiteral("label"), RelationVocabulary::label(found.type, other.gender)},
99
1/1
✓ Branch 86 → 87 taken 506 times.
1012 {QStringLiteral("holderLabel"), RelationVocabulary::holderLabel(found.type, true, holderGender)},
100 1012 {QStringLiteral("sentence"),
101
1/1
✓ Branch 92 → 93 taken 506 times.
1012 RelationVocabulary::sentence(other.name, found.type, displayName, other.gender)},
102 1012 {QStringLiteral("tooltip"), because},
103 1012 {QStringLiteral("otherKey"), other.key},
104
1/3
✗ Branch 105 → 106 not taken.
✓ Branch 105 → 107 taken 506 times.
✗ Branch 106 → 108 not taken.
1012 {QStringLiteral("otherName"), isMe ? tr("Me") : other.name},
105 1012 {QStringLiteral("isMe"), isMe},
106 1012 {QStringLiteral("initials"), other.look.initials},
107 1012 {QStringLiteral("avatarColor"), other.look.color},
108 1012 {QStringLiteral("hasPhoto"), other.look.hasPhoto},
109 1012 {QStringLiteral("storedOnKey"), QString()},
110 1012 {QStringLiteral("readOnly"), true},
111 1012 {QStringLiteral("inferred"), true},
112 1012 {QStringLiteral("holderKey"), holderKey}, // *Save as relationship* writes it on this card
113 1012 {QStringLiteral("sourceIndex"), -1},
114 1012 {QStringLiteral("mirrorable"), false}});
115 750 }
116 244 return rows;
117 26403 }
118
119 5668 int categoryRank(const QString &category) {
120
5/10
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 24 taken 5665 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 24 not taken.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 18 → 20 taken 3 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
5680 static const QStringList order{QStringLiteral("family"), QStringLiteral("friends"), QStringLiteral("work")};
121 5668 const int index = int(order.indexOf(category));
122
2/2
✓ Branch 25 → 26 taken 5416 times.
✓ Branch 25 → 27 taken 252 times.
5668 return index < 0 ? 3 : index;
123 12 }
124
125 // `viaRows` receives the introducers stored on this card, resolved: the
126 // editor's *Through* chips show their names (a link "urn:uuid:…" has none in
127 // the vCard) by the line's sourceIndex.
128 750 QVariantList relations(const QSqlDatabase &db, qint64 cardId, const QString &displayName, const QString &meKey,
129 QList<MetSentence::Person> *via, QVariantList *viaRows) {
130 750 QList<QVariantMap> rows;
131 750 QString holderGender; // F103-37: "Sister of", "Mother of"
132 {
133
1/1
✓ Branch 2 → 3 taken 750 times.
750 QSqlQuery gender(db);
134
1/1
✓ Branch 6 → 7 taken 750 times.
750 gender.prepare(QStringLiteral("SELECT gender FROM card_traits WHERE card_id = ?"));
135
1/1
✓ Branch 11 → 12 taken 750 times.
750 gender.addBindValue(cardId);
136
7/8
✓ Branch 13 → 14 taken 750 times.
✓ Branch 14 → 15 taken 750 times.
✗ Branch 14 → 18 not taken.
✓ Branch 15 → 16 taken 750 times.
✓ Branch 16 → 17 taken 230 times.
✓ Branch 16 → 18 taken 520 times.
✓ Branch 19 → 20 taken 230 times.
✓ Branch 19 → 26 taken 520 times.
750 if (gender.exec() && gender.next())
137
2/2
✓ Branch 20 → 21 taken 230 times.
✓ Branch 21 → 22 taken 230 times.
230 holderGender = gender.value(0).toString();
138 750 }
139
5/5
✓ Branch 27 → 28 taken 750 times.
✓ Branch 28 → 29 taken 750 times.
✓ Branch 29 → 30 taken 750 times.
✓ Branch 299 → 31 taken 876 times.
✓ Branch 299 → 300 taken 750 times.
1626 for (const RelationIndex::Row &row : RelationIndex::forCard(db, cardId)) {
140
2/2
✓ Branch 34 → 35 taken 72 times.
✓ Branch 34 → 89 taken 804 times.
876 if (row.role == QLatin1String("via")) {
141 72 via->append({row.otherName, row.otherKey});
142
2/3
✓ Branch 39 → 40 taken 72 times.
✗ Branch 39 → 41 not taken.
✓ Branch 40 → 42 taken 72 times.
72 const Other other = row.otherCardId > 0 ? otherCard(db, row.otherCardId) : Other{};
143
1/1
✓ Branch 68 → 69 taken 72 times.
216 viaRows->append(QVariantMap{{QStringLiteral("sourceIndex"), row.sourceIndex},
144 144 {QStringLiteral("otherName"), row.otherName},
145 144 {QStringLiteral("otherKey"), row.otherKey},
146 144 {QStringLiteral("initials"), other.initials},
147 144 {QStringLiteral("avatarColor"), other.color},
148
2/4
✓ Branch 72 → 73 taken 432 times.
✓ Branch 72 → 74 taken 72 times.
✗ Branch 349 → 350 not taken.
✗ Branch 349 → 351 not taken.
576 {QStringLiteral("hasPhoto"), other.hasPhoto}});
149 72 continue;
150 72 }
151
2/2
✓ Branch 91 → 92 taken 57 times.
✓ Branch 91 → 93 taken 747 times.
804 if (row.role == QLatin1String("introduced"))
152 57 continue; // shown in the history and the graph, not as a row
153 747 const bool inverse = row.role == QLatin1String("inverse");
154
3/3
✓ Branch 95 → 96 taken 367 times.
✓ Branch 95 → 97 taken 380 times.
✓ Branch 96 → 98 taken 367 times.
747 const QString shownType = inverse ? RelationVocabulary::inverseOf(row.type) : row.type;
155
2/2
✓ Branch 98 → 99 taken 367 times.
✓ Branch 98 → 100 taken 380 times.
747 const QString label = inverse ? RelationVocabulary::inverseLabel(row.type, row.otherGender, row.label)
156
2/2
✓ Branch 99 → 101 taken 367 times.
✓ Branch 100 → 101 taken 380 times.
747 : RelationVocabulary::label(row.type, row.otherGender, row.label);
157 // The sentence keeps the stored direction: with an inverse type it names
158 // the other person, without one (custom labels) this card's person.
159 747 QString sentence;
160
2/2
✓ Branch 101 → 102 taken 380 times.
✓ Branch 101 → 106 taken 367 times.
747 if (!inverse)
161
1/1
✓ Branch 102 → 103 taken 380 times.
380 sentence = RelationVocabulary::sentence(row.otherName, row.type, displayName, row.otherGender, row.label);
162
2/2
✓ Branch 107 → 108 taken 359 times.
✓ Branch 107 → 114 taken 8 times.
367 else if (!shownType.isEmpty())
163
1/1
✓ Branch 109 → 110 taken 359 times.
359 sentence = RelationVocabulary::sentence(row.otherName, shownType, displayName, row.otherGender);
164 else
165
1/1
✓ Branch 115 → 116 taken 8 times.
8 sentence = RelationVocabulary::sentence(displayName, row.type, row.otherName, {}, row.label);
166 747 QString tooltip = sentence;
167
2/2
✓ Branch 121 → 122 taken 367 times.
✓ Branch 121 → 128 taken 380 times.
747 if (inverse)
168 //: Tooltip of a relationship shown on the other card (D-5): "… — saved on Tom Becker's card".
169
2/2
✓ Branch 122 → 123 taken 367 times.
✓ Branch 123 → 124 taken 367 times.
367 tooltip = tr("%1 — saved on %2's card").arg(sentence, row.otherName);
170
1/2
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 133 taken 380 times.
380 else if (row.dangling)
171 ✗ tooltip = tr("This contact no longer exists");
172
2/2
✓ Branch 133 → 134 taken 10 times.
✓ Branch 133 → 138 taken 370 times.
380 else if (row.textOnly)
173
1/1
✓ Branch 134 → 135 taken 10 times.
10 tooltip = tr("Not in your contacts");
174
3/3
✓ Branch 138 → 139 taken 737 times.
✓ Branch 138 → 140 taken 10 times.
✓ Branch 139 → 141 taken 737 times.
747 const Other other = row.otherCardId > 0 ? otherCard(db, row.otherCardId) : Other{};
175
4/4
✓ Branch 142 → 143 taken 53 times.
✓ Branch 142 → 146 taken 694 times.
✓ Branch 144 → 145 taken 5 times.
✓ Branch 144 → 146 taken 48 times.
747 const bool isMe = !meKey.isEmpty() && row.otherKey == meKey;
176
1/1
✓ Branch 242 → 243 taken 747 times.
2241 rows.append(QVariantMap{{QStringLiteral("role"), row.role},
177 1494 {QStringLiteral("type"), row.type},
178 1494 {QStringLiteral("category"),
179
2/2
✓ Branch 158 → 159 taken 747 times.
✓ Branch 159 → 160 taken 747 times.
1494 RelationVocabulary::categoryId(RelationVocabulary::categoryOf(row.type))},
180 1494 {QStringLiteral("label"), label},
181
1/1
✓ Branch 168 → 169 taken 747 times.
1494 {QStringLiteral("holderLabel"), RelationVocabulary::holderLabel(row.type, !inverse, holderGender)},
182 1494 {QStringLiteral("sentence"), sentence},
183 1494 {QStringLiteral("tooltip"), tooltip},
184 1494 {QStringLiteral("otherKey"), row.otherKey},
185
3/3
✓ Branch 185 → 186 taken 5 times.
✓ Branch 185 → 187 taken 742 times.
✓ Branch 186 → 188 taken 5 times.
1494 {QStringLiteral("otherName"), isMe ? tr("Me") : row.otherName},
186 1494 {QStringLiteral("isMe"), isMe},
187 1494 {QStringLiteral("initials"), other.initials},
188 1494 {QStringLiteral("avatarColor"), other.color},
189 1494 {QStringLiteral("hasPhoto"), other.hasPhoto},
190 1494 {QStringLiteral("storedOnKey"), row.storedOnKey},
191 1494 {QStringLiteral("readOnly"), row.readOnly},
192 1494 {QStringLiteral("dangling"), row.dangling},
193 1494 {QStringLiteral("textOnly"), row.textOnly},
194 1494 {QStringLiteral("sourceIndex"), row.sourceIndex},
195 // F103-22: a row saved on the other card that this card's editor
196 // can change or remove (its type seen from here).
197 1494 {QStringLiteral("shownType"), shownType},
198 1494 {QStringLiteral("mirrorable"),
199
9/12
✓ Branch 232 → 233 taken 367 times.
✓ Branch 232 → 239 taken 380 times.
✓ Branch 234 → 235 taken 359 times.
✓ Branch 234 → 239 taken 8 times.
✓ Branch 235 → 236 taken 358 times.
✓ Branch 235 → 239 taken 1 time.
✓ Branch 237 → 238 taken 358 times.
✗ Branch 237 → 239 not taken.
✓ Branch 245 → 246 taken 14940 times.
✓ Branch 245 → 247 taken 747 times.
✗ Branch 417 → 418 not taken.
✗ Branch 417 → 419 not taken.
16434 inverse && !shownType.isEmpty() && !row.readOnly && !row.otherKey.isEmpty()}});
200 1497 }
201
2/2
✓ Branch 301 → 302 taken 750 times.
✓ Branch 302 → 303 taken 750 times.
750 rows += inferredRows(db, cardId, displayName, holderGender, meKey); // F103-31
202
3/3
✓ Branch 304 → 305 taken 750 times.
✓ Branch 305 → 306 taken 750 times.
✓ Branch 306 → 307 taken 750 times.
750 std::stable_sort(rows.begin(), rows.end(), [](const QVariantMap &a, const QVariantMap &b) {
203
3/3
✓ Branch 6 → 7 taken 2834 times.
✓ Branch 7 → 8 taken 2834 times.
✓ Branch 8 → 9 taken 2834 times.
5668 const int ra = categoryRank(a.value(QStringLiteral("category")).toString());
204
3/3
✓ Branch 18 → 19 taken 2834 times.
✓ Branch 19 → 20 taken 2834 times.
✓ Branch 20 → 21 taken 2834 times.
5668 const int rb = categoryRank(b.value(QStringLiteral("category")).toString());
205
2/2
✓ Branch 26 → 27 taken 564 times.
✓ Branch 26 → 28 taken 2270 times.
2834 if (ra != rb)
206 564 return ra < rb;
207
3/3
✓ Branch 32 → 33 taken 2270 times.
✓ Branch 33 → 34 taken 2270 times.
✓ Branch 40 → 41 taken 2270 times.
9080 return a.value(QStringLiteral("otherName")).toString().localeAwareCompare(
208
2/2
✓ Branch 38 → 39 taken 2270 times.
✓ Branch 39 → 40 taken 2270 times.
6810 b.value(QStringLiteral("otherName")).toString()) < 0;
209 });
210 750 QVariantList out;
211
4/4
✓ Branch 307 → 308 taken 750 times.
✓ Branch 308 → 309 taken 750 times.
✓ Branch 316 → 310 taken 1253 times.
✓ Branch 316 → 317 taken 750 times.
2003 for (const QVariantMap &row : rows)
212
1/1
✓ Branch 312 → 313 taken 1253 times.
1253 out.append(row);
213 750 return out;
214 17013 }
215
216 750 QVariantMap metMap(const ContactCard &card, const QList<MetSentence::Person> &via, bool placeIsCity,
217 const QString &eventTitle) {
218
6/7
✓ Branch 2 → 3 taken 750 times.
✓ Branch 3 → 4 taken 660 times.
✓ Branch 3 → 7 taken 90 times.
✓ Branch 5 → 6 taken 660 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 660 times.
✓ Branch 8 → 10 taken 90 times.
750 if (card.met.isEmpty() && via.isEmpty())
219 660 return {};
220 90 MetSentence::Input input;
221 90 input.eventTitle = eventTitle;
222 90 input.date = card.met.date;
223 90 input.place = card.met.place;
224 90 input.placeIsCity = placeIsCity;
225 90 input.channel = card.met.channel;
226 90 input.via = via;
227 // Introducers MailJD cannot resolve keep the name the card stores.
228
2/2
✓ Branch 15 → 16 taken 20 times.
✓ Branch 15 → 30 taken 70 times.
90 if (input.via.isEmpty())
229
1/2
✗ Branch 28 → 18 not taken.
✓ Branch 28 → 29 taken 20 times.
20 for (const ContactPersonRef &ref : card.met.via)
230 ✗ input.via.append({ref.name.isEmpty() ? ref.uri : ref.name, {}});
231
2/2
✓ Branch 30 → 31 taken 90 times.
✓ Branch 31 → 32 taken 90 times.
90 const QList<MetSentence::Segment> segments = MetSentence::build(input);
232 90 QVariantList list;
233
2/2
✓ Branch 58 → 35 taken 487 times.
✓ Branch 58 → 59 taken 90 times.
577 for (const MetSentence::Segment &segment : segments)
234
3/5
✓ Branch 46 → 47 taken 487 times.
✓ Branch 50 → 51 taken 974 times.
✓ Branch 50 → 52 taken 487 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
2435 list.append(QVariantMap{{QStringLiteral("text"), segment.text}, {QStringLiteral("link"), segment.link}});
235 180 return {{QStringLiteral("segments"), list},
236
1/1
✓ Branch 66 → 67 taken 90 times.
180 {QStringLiteral("text"), MetSentence::plainText(segments)},
237 180 {QStringLiteral("note"), card.met.note},
238
2/4
✓ Branch 78 → 79 taken 360 times.
✓ Branch 78 → 80 taken 90 times.
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 136 not taken.
540 {QStringLiteral("eventUid"), card.met.eventUid}};
239 2001 }
240
241 750 QVariantList socialList(const ContactCard &card) {
242 750 QVariantList out;
243
1/1
✓ Branch 2 → 3 taken 750 times.
750 const ServiceCatalog &catalog = ServiceCatalog::builtIn();
244
2/2
✓ Branch 83 → 5 taken 126 times.
✓ Branch 83 → 84 taken 750 times.
876 for (const ContactSocial &social : card.social) {
245
1/1
✓ Branch 6 → 7 taken 126 times.
126 const ServiceCatalog::Recognised described = catalog.describe(social);
246
1/1
✓ Branch 7 → 8 taken 126 times.
126 const ServiceCatalog::Service *service = catalog.find(described.service);
247
1/1
✓ Branch 9 → 10 taken 126 times.
126 QString label = catalog.displayName(described.service);
248
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 21 taken 126 times.
126 if (label.isEmpty())
249 ✗ label = social.kind == QLatin1String("chat") ? tr("Chat") : tr("Profile");
250
1/1
✓ Branch 59 → 60 taken 126 times.
378 out.append(QVariantMap{{QStringLiteral("kind"), social.kind},
251 252 {QStringLiteral("service"), described.service},
252 252 {QStringLiteral("label"), label},
253 252 {QStringLiteral("handle"), described.handle.isEmpty() ? social.value : described.handle},
254 252 {QStringLiteral("url"), described.url},
255 252 {QStringLiteral("value"), social.value},
256 // MDI code points; link-variant for services MailJD does not know.
257
4/8
✓ Branch 52 → 53 taken 126 times.
✗ Branch 52 → 55 not taken.
✓ Branch 53 → 54 taken 126 times.
✗ Branch 53 → 55 not taken.
✓ Branch 63 → 64 taken 882 times.
✓ Branch 63 → 65 taken 126 times.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
1134 {QStringLiteral("glyph"), int(service && service->codepoint ? service->codepoint : 0xF0339)}});
258 126 }
259 750 return out;
260 1008 }
261
262 750 QString languageNames(const QStringList &codes) {
263 750 QStringList names;
264
2/2
✓ Branch 10 → 4 taken 113 times.
✓ Branch 10 → 11 taken 750 times.
863 for (const QString &code : codes)
265
2/2
✓ Branch 5 → 6 taken 113 times.
✓ Branch 6 → 7 taken 113 times.
113 names.append(languageName(code));
266
1/1
✓ Branch 14 → 15 taken 750 times.
1500 return names.join(QStringLiteral(", "));
267 750 }
268
269 750 QVariantList places(const QSqlDatabase &db, qint64 cardId, QString *homeZone, QString *homePlace, bool *metIsCity) {
270 750 QVariantList out;
271
1/1
✓ Branch 2 → 3 taken 750 times.
750 QSqlQuery q(db);
272
1/1
✓ Branch 6 → 7 taken 750 times.
750 q.prepare(QStringLiteral("SELECT kind, position, lat IS NOT NULL, precision, source, tz, place_name, lat, lon"
273 " FROM card_places WHERE card_id = ? ORDER BY kind, position"));
274
1/1
✓ Branch 11 → 12 taken 750 times.
750 q.addBindValue(cardId);
275
2/3
✓ Branch 13 → 14 taken 750 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 750 times.
750 if (!q.exec())
276 ✗ return out;
277
3/3
✓ Branch 140 → 141 taken 1226 times.
✓ Branch 141 → 17 taken 476 times.
✓ Branch 141 → 142 taken 750 times.
1226 while (q.next()) {
278
2/2
✓ Branch 17 → 18 taken 476 times.
✓ Branch 18 → 19 taken 476 times.
476 const QString kind = q.value(0).toString();
279
2/2
✓ Branch 20 → 21 taken 476 times.
✓ Branch 21 → 22 taken 476 times.
476 const QString zone = q.value(5).toString();
280
10/10
✓ Branch 24 → 25 taken 460 times.
✓ Branch 24 → 34 taken 16 times.
✓ Branch 26 → 27 taken 186 times.
✓ Branch 26 → 34 taken 274 times.
✓ Branch 29 → 30 taken 61 times.
✓ Branch 29 → 33 taken 125 times.
✓ Branch 32 → 33 taken 56 times.
✓ Branch 32 → 34 taken 5 times.
✓ Branch 35 → 36 taken 181 times.
✓ Branch 35 → 43 taken 295 times.
476 if (homeZone->isEmpty() && !zone.isEmpty() && (kind == QLatin1String("home") || kind == QLatin1String("work"))) {
281 181 *homeZone = zone;
282
2/2
✓ Branch 37 → 38 taken 181 times.
✓ Branch 38 → 39 taken 181 times.
181 *homePlace = q.value(6).toString();
283 }
284
2/2
✓ Branch 45 → 46 taken 83 times.
✓ Branch 45 → 53 taken 393 times.
476 if (kind == QLatin1String("met"))
285
2/2
✓ Branch 47 → 48 taken 83 times.
✓ Branch 48 → 49 taken 83 times.
83 *metIsCity = q.value(3).toString() == QLatin1String("city");
286
1/1
✓ Branch 103 → 104 taken 476 times.
1428 out.append(QVariantMap{{QStringLiteral("kind"), kind},
287
2/2
✓ Branch 60 → 61 taken 476 times.
✓ Branch 61 → 62 taken 476 times.
952 {QStringLiteral("position"), q.value(1).toInt()},
288
2/2
✓ Branch 66 → 67 taken 476 times.
✓ Branch 67 → 68 taken 476 times.
952 {QStringLiteral("placed"), q.value(2).toBool()},
289
2/2
✓ Branch 72 → 73 taken 476 times.
✓ Branch 73 → 74 taken 476 times.
952 {QStringLiteral("precision"), q.value(3).toString()},
290
2/2
✓ Branch 78 → 79 taken 476 times.
✓ Branch 79 → 80 taken 476 times.
952 {QStringLiteral("source"), q.value(4).toString()},
291 952 {QStringLiteral("timeZone"), zone},
292
2/2
✓ Branch 88 → 89 taken 476 times.
✓ Branch 89 → 90 taken 476 times.
952 {QStringLiteral("placeName"), q.value(6).toString()},
293
1/1
✓ Branch 94 → 95 taken 476 times.
952 {QStringLiteral("latitude"), q.value(7)},
294
3/5
✓ Branch 99 → 100 taken 476 times.
✓ Branch 107 → 108 taken 4284 times.
✓ Branch 107 → 109 taken 476 times.
✗ Branch 176 → 177 not taken.
✗ Branch 176 → 178 not taken.
5236 {QStringLiteral("longitude"), q.value(8)}});
295 476 }
296 750 return out;
297 5510 }
298
299 } // namespace
300
301 127 QString languageName(const QString &code) {
302
1/1
✓ Branch 2 → 3 taken 127 times.
127 const QLocale locale(code);
303
2/3
✓ Branch 3 → 4 taken 127 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 127 times.
127 if (locale.language() == QLocale::C)
304 ✗ return code;
305
1/1
✓ Branch 6 → 7 taken 127 times.
127 QString name = locale.nativeLanguageName();
306 // A code without a region ("en") names the language, not its default
307 // territory's variant ("American English"): the shortest native name.
308
5/8
✓ Branch 9 → 10 taken 127 times.
✓ Branch 10 → 11 taken 127 times.
✗ Branch 10 → 16 not taken.
✓ Branch 13 → 14 taken 127 times.
✓ Branch 14 → 15 taken 127 times.
✗ Branch 14 → 16 not taken.
✓ Branch 17 → 18 taken 127 times.
✗ Branch 17 → 41 not taken.
127 if (!code.contains(QLatin1Char('-')) && !code.contains(QLatin1Char('_')))
309
7/7
✓ Branch 18 → 19 taken 127 times.
✓ Branch 19 → 20 taken 127 times.
✓ Branch 20 → 21 taken 127 times.
✓ Branch 21 → 22 taken 127 times.
✓ Branch 22 → 23 taken 127 times.
✓ Branch 38 → 24 taken 6443 times.
✓ Branch 38 → 39 taken 127 times.
6570 for (const QLocale &variant : QLocale::matchingLocales(locale.language(), locale.script(), QLocale::AnyTerritory))
310
6/7
✓ Branch 25 → 26 taken 6443 times.
✓ Branch 27 → 28 taken 6443 times.
✗ Branch 27 → 32 not taken.
✓ Branch 30 → 31 taken 53 times.
✓ Branch 30 → 32 taken 6390 times.
✓ Branch 33 → 34 taken 53 times.
✓ Branch 33 → 35 taken 6390 times.
6443 if (const QString native = variant.nativeLanguageName(); !native.isEmpty() && native.size() < name.size())
311 6570 name = native;
312
1/2
✓ Branch 42 → 43 taken 127 times.
✗ Branch 42 → 47 not taken.
127 if (!name.isEmpty())
313
1/1
✓ Branch 45 → 46 taken 127 times.
127 name[0] = name.at(0).toUpper();
314
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 127 times.
127 return name.isEmpty() ? code : name;
315 127 }
316
317 750 QString genderName(const QString &sex) {
318
3/3
✓ Branch 2 → 3 taken 750 times.
✓ Branch 3 → 4 taken 750 times.
✓ Branch 4 → 5 taken 750 times.
750 const QString letter = sex.trimmed().left(1).toUpper();
319
2/2
✓ Branch 8 → 9 taken 520 times.
✓ Branch 8 → 10 taken 230 times.
750 if (letter.isEmpty())
320 520 return {};
321 //: Gender of a contact (vCard GENDER F).
322
2/2
✓ Branch 12 → 13 taken 157 times.
✓ Branch 12 → 15 taken 73 times.
230 if (letter == QLatin1String("F"))
323
1/1
✓ Branch 13 → 14 taken 157 times.
157 return tr("Female");
324 //: Gender of a contact (vCard GENDER M).
325
2/2
✓ Branch 17 → 18 taken 72 times.
✓ Branch 17 → 20 taken 1 time.
73 if (letter == QLatin1String("M"))
326
1/1
✓ Branch 18 → 19 taken 72 times.
72 return tr("Male");
327 //: Gender of a contact (vCard GENDER O): German "divers".
328
1/2
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 25 taken 1 time.
1 if (letter == QLatin1String("O"))
329 ✗ return tr("Other");
330 //: Gender of a contact (vCard GENDER U or N): German "keine Angabe".
331
2/6
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 31 taken 1 time.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 36 not taken.
1 if (letter == QLatin1String("U") || letter == QLatin1String("N"))
332
1/1
✓ Branch 34 → 35 taken 1 time.
1 return tr("Not specified");
333 ✗ return sex.trimmed();
334 750 }
335
336 750 QVariantMap build(const QSqlDatabase &db, qint64 cardId, const ContactCard &card, const QString &displayName,
337 const QString &meKey, const EventTitle &eventTitle) {
338 750 QVariantMap view;
339 750 QString zone;
340 750 QString zonePlace;
341 750 bool metIsCity = false;
342
2/2
✓ Branch 2 → 3 taken 750 times.
✓ Branch 7 → 8 taken 750 times.
1500 view.insert(QStringLiteral("places"), places(db, cardId, &zone, &zonePlace, &metIsCity));
343 750 QList<MetSentence::Person> via;
344 750 QVariantList viaRows;
345 // F103-13: a family's members are shown apart from the relationships.
346 750 QVariantList others, members;
347
5/5
✓ Branch 12 → 13 taken 750 times.
✓ Branch 13 → 14 taken 750 times.
✓ Branch 14 → 15 taken 750 times.
✓ Branch 61 → 16 taken 1253 times.
✓ Branch 61 → 62 taken 750 times.
2003 for (const QVariant &row : relations(db, cardId, displayName, meKey, &via, &viaRows)) {
348
1/1
✓ Branch 17 → 18 taken 1253 times.
1253 const QVariantMap map = row.toMap();
349
4/4
✓ Branch 22 → 23 taken 1253 times.
✓ Branch 23 → 24 taken 1253 times.
✓ Branch 32 → 33 taken 1253 times.
✓ Branch 33 → 34 taken 1253 times.
3759 const QString role = map.value(QStringLiteral("role")).toString(), type = map.value(QStringLiteral("type")).toString();
350
6/6
✓ Branch 40 → 41 taken 380 times.
✓ Branch 40 → 44 taken 873 times.
✓ Branch 43 → 44 taken 325 times.
✓ Branch 43 → 50 taken 55 times.
✓ Branch 46 → 47 taken 367 times.
✓ Branch 46 → 51 taken 831 times.
2818 const bool member = (role == QLatin1String("relation") && type == QLatin1String("member")) ||
351
2/2
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 51 taken 366 times.
1565 (role == QLatin1String("inverse") && type == QLatin1String("member-of"));
352
3/3
✓ Branch 52 → 53 taken 56 times.
✓ Branch 52 → 54 taken 1197 times.
✓ Branch 55 → 56 taken 1253 times.
1253 (member ? members : others).append(row);
353 2003 }
354
1/1
✓ Branch 67 → 68 taken 750 times.
1500 view.insert(QStringLiteral("relations"), others);
355
1/1
✓ Branch 75 → 76 taken 750 times.
1500 view.insert(QStringLiteral("members"), members);
356
1/1
✓ Branch 83 → 84 taken 750 times.
1500 view.insert(QStringLiteral("via"), viaRows);
357
5/5
✓ Branch 88 → 89 taken 595 times.
✓ Branch 88 → 93 taken 155 times.
✓ Branch 90 → 91 taken 19 times.
✓ Branch 90 → 93 taken 576 times.
✓ Branch 91 → 92 taken 19 times.
750 const QString occasion = eventTitle && !card.met.eventUid.isEmpty() ? eventTitle(card.met.eventUid) : QString();
358
2/2
✓ Branch 94 → 95 taken 750 times.
✓ Branch 99 → 100 taken 750 times.
1500 view.insert(QStringLiteral("met"), metMap(card, via, metIsCity, occasion));
359
2/2
✓ Branch 104 → 105 taken 750 times.
✓ Branch 109 → 110 taken 750 times.
1500 view.insert(QStringLiteral("social"), socialList(card));
360
2/2
✓ Branch 114 → 115 taken 750 times.
✓ Branch 119 → 120 taken 750 times.
1500 view.insert(QStringLiteral("languages"), languageNames(card.languages));
361
2/2
✓ Branch 127 → 128 taken 750 times.
✓ Branch 132 → 133 taken 750 times.
2250 view.insert(QStringLiteral("interests"), card.interests.join(QStringLiteral(" · ")));
362 750 QVariantList dates;
363
2/2
✓ Branch 173 → 141 taken 26 times.
✓ Branch 173 → 174 taken 750 times.
776 for (const ContactDate &date : card.otherDates)
364
2/4
✗ Branch 146 → 147 not taken.
✓ Branch 146 → 148 taken 26 times.
✗ Branch 147 → 149 not taken.
✓ Branch 158 → 159 taken 26 times.
78 dates.append(QVariantMap{{QStringLiteral("label"), date.label.isEmpty() ? tr("Date") : date.label},
365
4/6
✓ Branch 153 → 154 taken 26 times.
✓ Branch 154 → 155 taken 26 times.
✓ Branch 162 → 163 taken 52 times.
✓ Branch 162 → 164 taken 26 times.
✗ Branch 474 → 475 not taken.
✗ Branch 474 → 476 not taken.
130 {QStringLiteral("text"), ContactEditorFields::formatPartialDate(date.date)}});
366
1/1
✓ Branch 178 → 179 taken 750 times.
1500 view.insert(QStringLiteral("otherDates"), dates);
367
1/1
✓ Branch 186 → 187 taken 750 times.
1500 view.insert(QStringLiteral("birthplace"), card.birthplace);
368
1/1
✓ Branch 190 → 191 taken 750 times.
750 QString gender = genderName(card.gender);
369
4/6
✓ Branch 192 → 193 taken 230 times.
✓ Branch 192 → 196 taken 520 times.
✗ Branch 194 → 195 not taken.
✓ Branch 194 → 196 taken 230 times.
✗ Branch 197 → 198 not taken.
✓ Branch 197 → 207 taken 750 times.
750 if (!gender.isEmpty() && !card.genderIdentity.isEmpty())
370 ✗ gender += QStringLiteral(" · ") + card.genderIdentity;
371
1/1
✓ Branch 211 → 212 taken 750 times.
1500 view.insert(QStringLiteral("gender"), gender);
372 750 QVariantList links; // D-15, for the history
373
2/2
✓ Branch 246 → 217 taken 71 times.
✓ Branch 246 → 247 taken 750 times.
821 for (const ContactMailLink &link : card.mailLinks)
374
1/1
✓ Branch 232 → 233 taken 71 times.
213 links.append(QVariantMap{{QStringLiteral("messageId"), link.messageId},
375 142 {QStringLiteral("subject"), link.subject},
376
2/4
✓ Branch 236 → 237 taken 213 times.
✓ Branch 236 → 238 taken 71 times.
✗ Branch 548 → 549 not taken.
✗ Branch 548 → 550 not taken.
426 {QStringLiteral("date"), link.date}});
377
1/1
✓ Branch 251 → 252 taken 750 times.
1500 view.insert(QStringLiteral("mailLinks"), links);
378 // I-6: TZ of the card first, then the zone of the home/work place.
379
2/3
✓ Branch 257 → 258 taken 750 times.
✗ Branch 258 → 259 not taken.
✓ Branch 258 → 260 taken 750 times.
750 const QString cardZone = card.timeZone.contains(QLatin1Char('/')) ? card.timeZone : QString();
380
1/2
✓ Branch 262 → 263 taken 750 times.
✗ Branch 262 → 264 not taken.
750 const QString personZone = cardZone.isEmpty() ? zone : cardZone;
381
2/2
✓ Branch 267 → 268 taken 181 times.
✓ Branch 267 → 328 taken 569 times.
750 if (!personZone.isEmpty()) {
382
2/2
✓ Branch 268 → 269 taken 181 times.
✓ Branch 269 → 270 taken 181 times.
181 const QTimeZone theirs(personZone.toUtf8());
383
1/1
✓ Branch 271 → 272 taken 181 times.
181 const QDateTime now = QDateTime::currentDateTimeUtc();
384
10/14
✓ Branch 272 → 273 taken 181 times.
✓ Branch 273 → 274 taken 181 times.
✗ Branch 273 → 279 not taken.
✓ Branch 274 → 275 taken 181 times.
✓ Branch 275 → 276 taken 181 times.
✓ Branch 276 → 277 taken 181 times.
✓ Branch 277 → 278 taken 179 times.
✓ Branch 277 → 279 taken 2 times.
✓ Branch 280 → 281 taken 181 times.
✗ Branch 280 → 282 not taken.
✓ Branch 282 → 283 taken 179 times.
✓ Branch 282 → 325 taken 2 times.
✗ Branch 592 → 593 not taken.
✗ Branch 592 → 594 not taken.
181 if (theirs.isValid() && theirs.offsetFromUtc(now) != QTimeZone::systemTimeZone().offsetFromUtc(now))
385
1/1
✓ Branch 308 → 309 taken 179 times.
358 view.insert(QStringLiteral("localTime"),
386 537 QVariantMap{{QStringLiteral("zone"), personZone},
387
1/2
✓ Branch 291 → 292 taken 179 times.
✗ Branch 291 → 293 not taken.
358 {QStringLiteral("place"), cardZone.isEmpty() ? zonePlace : QString()},
388
4/8
✓ Branch 299 → 300 taken 179 times.
✗ Branch 299 → 301 not taken.
✓ Branch 300 → 302 taken 179 times.
✗ Branch 301 → 302 not taken.
✓ Branch 314 → 315 taken 537 times.
✓ Branch 314 → 316 taken 179 times.
✗ Branch 607 → 608 not taken.
✗ Branch 607 → 609 not taken.
1074 {QStringLiteral("source"), cardZone.isEmpty() ? tr("From the address") : tr("From the card")}});
389 181 }
390 750 return view;
391 1828 }
392
393 } // namespace ContactPeopleView
394