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: 89.3% 400 / 0 / 448
Functions: 94.4% 34 / 0 / 36
Branches: 76.4% 709 / 0 / 928

presentation/QuickContactActionsPeople.cpp
Line Branch Exec Source
1 // Sprint 103 relationship writes of QuickContactActions (S103.3.1/S103.3.8,
2 // D-5): a relationship is one RELATED line on one card; adding writes it on
3 // the card it is added from, removing deletes the line from the card that
4 // stores it (the inverse row of the other card disappears with it). Both
5 // leave an "edit" undo step like a saved edit.
6
7 #include "QuickContactActions.h"
8
9 #include "ContactEditorPeople.h"
10 #include "ContactPeopleView.h"
11 #include "MetSentence.h"
12 #include "app/core/ContactOutbox.h"
13 #include "app/core/ContactSyncCoordinator.h"
14 #include "data/ContactIndex.h"
15 #include "data/ContactQueries.h"
16 #include "data/ContactStore.h"
17 #include "data/RelationIndex.h"
18 #include "data/RelationVocabulary.h"
19 #include "data/ServiceCatalog.h"
20 #include "data/VCardCodec.h"
21
22 #include <QHash>
23 #include <QLocale>
24 #include <QMap>
25 #include <QLoggingCategory>
26 #include <QRegularExpression>
27 #include <QSet>
28 #include <QSettings>
29 #include <QSqlQuery>
30 #include <algorithm>
31
32
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 116 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
119 Q_LOGGING_CATEGORY(lcContactRelationActions, "mailjd.contacts.relations")
33
34 namespace {
35
36
3/9
✓ Branch 10 → 11 taken 10 times.
✓ Branch 12 → 13 taken 20 times.
✓ Branch 12 → 14 taken 10 times.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 41 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
50 QVariantMap failure(const QString &error) { return {{QStringLiteral("ok"), false}, {QStringLiteral("error"), error}}; }
37
38 } // namespace
39
40 113 QVariantMap QuickContactActions::editPeople(const QString &key, const std::function<QString(ContactCard *)> &change,
41 const QString &message) {
42
1/1
✓ Branch 2 → 3 taken 113 times.
113 ContactSyncCoordinator *sync = m_coordinator();
43 113 qint64 bookId = 0;
44 113 QString uid;
45
4/7
✓ Branch 3 → 4 taken 113 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 113 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 113 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 113 times.
113 if (!sync || !ContactQueries::splitKey(key, &bookId, &uid))
46 ✗ return failure(tr("Contacts are not available right now."));
47
1/1
✓ Branch 15 → 16 taken 113 times.
113 const auto stored = sync->store()->card(bookId, uid);
48
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 23 taken 113 times.
113 if (!stored)
49 ✗ return failure(tr("The contact no longer exists."));
50
1/1
✓ Branch 23 → 24 taken 113 times.
113 ContactCard card;
51
2/3
✓ Branch 25 → 26 taken 113 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 32 taken 113 times.
113 if (!VCardCodec::parse(stored->raw, &card))
52 ✗ return failure(tr("This contact cannot be changed in MailJD. Edit it where it was created."));
53
3/3
✓ Branch 32 → 33 taken 113 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 37 taken 110 times.
113 if (const QString refused = change(&card); !refused.isEmpty())
54
3/3
✓ Branch 35 → 36 taken 3 times.
✓ Branch 39 → 40 taken 110 times.
✓ Branch 39 → 43 taken 3 times.
113 return failure(refused);
55 110 QString error;
56
1/1
✓ Branch 42 → 44 taken 110 times.
110 const QString saved = sync->outbox()->saveCard(bookId, uid, card, &error);
57
2/2
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 48 taken 108 times.
110 if (saved.isEmpty())
58
1/1
✓ Branch 46 → 47 taken 2 times.
2 return failure(error);
59
1/1
✓ Branch 55 → 56 taken 108 times.
108 pushUndo(tr("Undo edit"), {QStringLiteral("edit"), *stored, key, bookId});
60
1/1
✓ Branch 59 → 60 taken 108 times.
108 report(message, true);
61
2/4
✓ Branch 70 → 71 taken 216 times.
✓ Branch 70 → 72 taken 108 times.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
648 return {{QStringLiteral("ok"), true}, {QStringLiteral("key"), saved}};
62 653 }
63
64 32 QVariantMap QuickContactActions::addRelation(const QString &key, const QString &otherKey, const QString &type,
65 const QString &label) {
66
1/1
✓ Branch 2 → 3 taken 32 times.
32 ContactSyncCoordinator *sync = m_coordinator();
67 32 qint64 otherBook = 0;
68 32 QString otherUid;
69 32 ContactRelation relation;
70
1/1
✓ Branch 3 → 4 taken 32 times.
32 relation.type = RelationVocabulary::canonical(type);
71
9/11
✓ Branch 8 → 9 taken 31 times.
✓ Branch 8 → 13 taken 1 time.
✓ Branch 9 → 10 taken 31 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 30 times.
✓ Branch 14 → 15 taken 31 times.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 22 taken 31 times.
✗ Branch 187 → 188 not taken.
✗ Branch 187 → 189 not taken.
32 if (relation.type != QLatin1String("custom") && !RelationVocabulary::editableTypes().contains(relation.type))
72
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
1 return failure(tr("Choose how the two are related."));
73 // A family's entry has no relationships besides its members (set on the family).
74
10/10
✓ Branch 22 → 23 taken 31 times.
✓ Branch 23 → 24 taken 29 times.
✓ Branch 23 → 26 taken 2 times.
✓ Branch 24 → 25 taken 29 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 30 taken 28 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 30 taken 1 time.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 37 taken 29 times.
31 if ((isFamily(key) || isFamily(otherKey)) && relation.type != QLatin1String("member"))
75
2/2
✓ Branch 32 → 33 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
2 return failure(tr("A family has no relationships of its own. Add people to it as members of the family."));
76 // S106-272: a member line belongs on a family's entry (F103-10); on the
77 // me card the D-6 flip below turned it into a member-of line on another
78 // person — inverted family data.
79
11/14
✓ Branch 39 → 40 taken 4 times.
✓ Branch 39 → 46 taken 25 times.
✓ Branch 40 → 41 taken 4 times.
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 46 taken 3 times.
✓ Branch 43 → 44 taken 1 time.
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 46 not taken.
✓ Branch 47 → 48 taken 4 times.
✓ Branch 47 → 49 taken 25 times.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 55 taken 28 times.
✗ Branch 199 → 200 not taken.
✗ Branch 199 → 201 not taken.
29 if (relation.type == QLatin1String("member") && key == meKey() && !isFamily(key))
80
2/2
✓ Branch 50 → 51 taken 1 time.
✓ Branch 51 → 52 taken 1 time.
1 return failure(tr("Choose how the two are related."));
81
2/2
✓ Branch 57 → 58 taken 1 time.
✓ Branch 57 → 68 taken 27 times.
28 if (relation.type == QLatin1String("custom")) {
82
1/1
✓ Branch 58 → 59 taken 1 time.
1 relation.label = label.trimmed();
83
1/2
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 68 taken 1 time.
1 if (relation.label.isEmpty())
84 ✗ return failure(tr("Enter a name for the relationship."));
85 }
86
6/7
✓ Branch 68 → 69 taken 28 times.
✗ Branch 68 → 72 not taken.
✓ Branch 69 → 70 taken 28 times.
✓ Branch 70 → 71 taken 26 times.
✓ Branch 70 → 72 taken 2 times.
✓ Branch 73 → 74 taken 26 times.
✓ Branch 73 → 102 taken 2 times.
28 if (sync && ContactQueries::splitKey(otherKey, &otherBook, &otherUid)) {
87
2/2
✓ Branch 75 → 76 taken 1 time.
✓ Branch 75 → 81 taken 25 times.
26 if (otherKey == key)
88
2/2
✓ Branch 76 → 77 taken 1 time.
✓ Branch 77 → 78 taken 1 time.
1 return failure(tr("A contact cannot be related to itself."));
89
1/1
✓ Branch 82 → 83 taken 25 times.
25 const auto other = sync->store()->card(otherBook, otherUid);
90
1/2
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 90 taken 25 times.
25 if (!other)
91 ✗ return failure(tr("The other contact no longer exists."));
92
1/1
✓ Branch 90 → 91 taken 25 times.
25 ContactCard parsed;
93
1/1
✓ Branch 92 → 93 taken 25 times.
25 VCardCodec::parse(other->raw, &parsed);
94 25 relation.targetUid = otherUid;
95 25 relation.targetName = parsed.formattedName;
96
1/2
✓ Branch 98 → 99 taken 25 times.
✗ Branch 98 → 101 not taken.
25 } else {
97 // A person who is not a contact ("Grandma Rosa"): the text form (D-3).
98
1/1
✓ Branch 102 → 103 taken 2 times.
2 relation.targetName = otherKey.trimmed();
99
1/2
✗ Branch 106 → 107 not taken.
✓ Branch 106 → 112 taken 2 times.
2 if (relation.targetName.isEmpty())
100 ✗ return failure(tr("Choose a person."));
101 }
102 // D-6: "Anna is my sister" is stored on Anna's card, pointing to me.
103 27 QString holder = key;
104
1/1
✓ Branch 113 → 114 taken 27 times.
27 const QString inverse = RelationVocabulary::inverseOf(relation.type);
105
8/13
✓ Branch 114 → 115 taken 27 times.
✓ Branch 116 → 117 taken 3 times.
✓ Branch 116 → 122 taken 24 times.
✓ Branch 118 → 119 taken 3 times.
✗ Branch 118 → 122 not taken.
✓ Branch 120 → 121 taken 3 times.
✗ Branch 120 → 122 not taken.
✓ Branch 123 → 124 taken 27 times.
✗ Branch 123 → 125 not taken.
✓ Branch 125 → 126 taken 3 times.
✓ Branch 125 → 146 taken 24 times.
✗ Branch 226 → 227 not taken.
✗ Branch 226 → 228 not taken.
27 if (key == meKey() && !relation.targetUid.isEmpty() && !inverse.isEmpty()) {
106 3 qint64 meBook = 0;
107 3 QString meUid;
108
1/1
✓ Branch 126 → 127 taken 3 times.
3 ContactQueries::splitKey(key, &meBook, &meUid);
109 3 holder = otherKey;
110 3 relation.type = inverse;
111 3 relation.targetUid = meUid;
112
3/3
✓ Branch 130 → 131 taken 3 times.
✓ Branch 135 → 136 taken 3 times.
✓ Branch 136 → 137 taken 3 times.
6 relation.targetName = personEntry(key).value(QStringLiteral("name")).toString();
113 3 }
114 27 const QVariantMap result = editPeople(
115 holder,
116 27 [&relation](ContactCard *card) -> QString {
117
4/4
✓ Branch 2 → 3 taken 27 times.
✓ Branch 3 → 4 taken 27 times.
✓ Branch 25 → 5 taken 6 times.
✓ Branch 25 → 26 taken 26 times.
32 for (const ContactRelation &existing : card->relations)
118
5/6
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 19 taken 4 times.
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 19 not taken.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 23 taken 5 times.
8 if (existing.type == relation.type && existing.label == relation.label &&
119
4/6
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 14 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 18 taken 1 time.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 19 taken 1 time.
3 ((!relation.targetUid.isEmpty() && existing.targetUid == relation.targetUid) ||
120
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
1 (relation.targetUid.isEmpty() && existing.targetName == relation.targetName)))
121
1/1
✓ Branch 21 → 22 taken 1 time.
1 return tr("This relationship is already saved.");
122
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 26 times.
26 if (card->relations.size() >= VCardCodec::MaxRelations)
123 ✗ return tr("This contact has too many relationships.");
124 26 card->relations.append(relation);
125 26 return {};
126 },
127
2/2
✓ Branch 146 → 147 taken 27 times.
✓ Branch 148 → 149 taken 27 times.
81 tr("Relationship added"));
128
8/8
✓ Branch 151 → 152 taken 27 times.
✓ Branch 155 → 156 taken 27 times.
✓ Branch 156 → 157 taken 27 times.
✓ Branch 157 → 158 taken 27 times.
✓ Branch 158 → 159 taken 27 times.
✓ Branch 159 → 160 taken 27 times.
✓ Branch 177 → 153 taken 27 times.
✓ Branch 177 → 178 taken 27 times.
54 qCInfo(lcContactRelationActions) << "Add relation" << relation.type << "on" << holder
129
5/5
✓ Branch 164 → 165 taken 27 times.
✓ Branch 165 → 166 taken 27 times.
✓ Branch 166 → 167 taken 26 times.
✓ Branch 166 → 168 taken 1 time.
✓ Branch 169 → 170 taken 27 times.
54 << (result.value(QStringLiteral("ok")).toBool() ? "done" : "refused");
130 27 return result;
131 32 }
132
133 4 QVariantMap QuickContactActions::removeRelation(const QString &storedOnKey, int sourceIndex) {
134 4 const QVariantMap result = editPeople(
135 storedOnKey,
136 4 [sourceIndex](ContactCard *card) -> QString {
137
2/2
✓ Branch 14 → 3 taken 5 times.
✓ Branch 14 → 15 taken 1 time.
6 for (int i = 0; i < card->relations.size(); ++i) {
138
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 3 times.
5 if (card->relations.at(i).sourceIndex != sourceIndex)
139 2 continue;
140
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3 times.
3 if (card->relations.at(i).readOnly())
141 ✗ return tr("This relationship was saved by another program and cannot be changed here.");
142 3 card->relations.removeAt(i);
143 3 return {};
144 }
145 1 return tr("The relationship no longer exists.");
146 },
147
2/2
✓ Branch 2 → 3 taken 4 times.
✓ Branch 4 → 5 taken 4 times.
12 tr("Relationship removed"));
148
8/8
✓ Branch 7 → 8 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
✓ Branch 13 → 14 taken 4 times.
✓ Branch 14 → 15 taken 4 times.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 33 → 9 taken 4 times.
✓ Branch 33 → 34 taken 4 times.
8 qCInfo(lcContactRelationActions) << "Remove relation" << sourceIndex << "on" << storedOnKey
149
5/5
✓ Branch 20 → 21 taken 4 times.
✓ Branch 21 → 22 taken 4 times.
✓ Branch 22 → 23 taken 3 times.
✓ Branch 22 → 24 taken 1 time.
✓ Branch 25 → 26 taken 4 times.
8 << (result.value(QStringLiteral("ok")).toBool() ? "done" : "refused");
150 4 return result;
151 ✗ }
152
153 // ─── pins (S103.4.5, D-10, interaction.md §6.6) ───
154
155 1 QVariantMap QuickContactActions::pinAddress(const QString &key, int addressIndex, double latitude, double longitude) {
156
1/1
✓ Branch 2 → 3 taken 1 time.
1 const GeoPoint position = GeoPoint::at(latitude, longitude);
157
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 1 time.
1 if (!position.valid)
158 ✗ return failure(tr("This position is outside the map."));
159 1 return editPeople(
160 key,
161
1/1
✓ Branch 10 → 11 taken 1 time.
1 [addressIndex, position](ContactCard *card) -> QString {
162
3/6
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (addressIndex < 0 || addressIndex >= card->addresses.size())
163 ✗ return tr("The address no longer exists.");
164 1 card->addresses[addressIndex].geo = position;
165 1 return {};
166 },
167
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
3 tr("Position saved"));
168 }
169
170 ✗ QVariantMap QuickContactActions::clearPin(const QString &key, int addressIndex) {
171 ✗ return editPeople(
172 key,
173 ✗ [addressIndex](ContactCard *card) -> QString {
174 ✗ if (addressIndex < 0 || addressIndex >= card->addresses.size())
175 ✗ return tr("The address no longer exists.");
176 ✗ card->addresses[addressIndex].geo = {};
177 ✗ return {};
178 },
179 ✗ tr("Computed position restored"));
180 }
181
182 // ─── editor helpers (S103.3.2/S103.3.3, interaction.md §3/§4) ───
183
184 183 QVariantList QuickContactActions::relationTypes(bool holderSide, const QString &holderGender) const {
185 183 QVariantList out;
186 183 QStringList seen;
187 9438 const auto append = [&](const QString &type, RelationVocabulary::Category category) {
188
2/2
✓ Branch 3 → 4 taken 4320 times.
✓ Branch 3 → 5 taken 5118 times.
9438 if (seen.contains(type))
189 4320 return;
190
1/1
✓ Branch 5 → 6 taken 5118 times.
5118 seen.append(type);
191
3/3
✓ Branch 6 → 7 taken 5040 times.
✓ Branch 6 → 8 taken 78 times.
✓ Branch 7 → 9 taken 5040 times.
5118 const QString holderLabel = holderSide ? RelationVocabulary::holderLabel(type, true, holderGender) : QString();
192
1/1
✓ Branch 35 → 36 taken 5118 times.
15354 out.append(QVariantMap{{QStringLiteral("id"), type},
193
7/11
✓ Branch 17 → 18 taken 78 times.
✓ Branch 17 → 21 taken 5040 times.
✓ Branch 20 → 22 taken 78 times.
✓ Branch 48 → 49 taken 78 times.
✓ Branch 48 → 50 taken 5040 times.
✓ Branch 50 → 51 taken 78 times.
✓ Branch 50 → 52 taken 5040 times.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 93 not taken.
10236 {QStringLiteral("label"), holderLabel.isEmpty() ? RelationVocabulary::label(type) : holderLabel},
194
1/1
✓ Branch 26 → 27 taken 5118 times.
10236 {QStringLiteral("category"), RelationVocabulary::categoryId(category)},
195
3/5
✓ Branch 31 → 32 taken 5118 times.
✓ Branch 39 → 40 taken 20472 times.
✓ Branch 39 → 41 taken 5118 times.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
30708 {QStringLiteral("categoryLabel"), RelationVocabulary::categoryLabel(category)}});
196
1/5
✓ Branch 33 → 34 taken 5118 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 112 not taken.
✗ Branch 110 → 111 not taken.
✗ Branch 110 → 112 not taken.
30708 };
197 915 for (const RelationVocabulary::Category category :
198 {RelationVocabulary::Category::Family, RelationVocabulary::Category::Friends,
199
2/2
✓ Branch 51 → 5 taken 732 times.
✓ Branch 51 → 52 taken 183 times.
1098 RelationVocabulary::Category::Work, RelationVocabulary::Category::Other}) {
200
1/1
✓ Branch 5 → 6 taken 732 times.
732 const QStringList types = RelationVocabulary::typesOf(category);
201
2/2
✓ Branch 30 → 8 taken 5124 times.
✓ Branch 30 → 31 taken 732 times.
5856 for (const QString &type : types) {
202 // F103-20: family membership is set on the family's entry (*Family members*),
203 // never as a relationship between two people (one of them became a family).
204
6/6
✓ Branch 11 → 12 taken 4941 times.
✓ Branch 11 → 15 taken 183 times.
✓ Branch 14 → 15 taken 183 times.
✓ Branch 14 → 16 taken 4758 times.
✓ Branch 17 → 18 taken 366 times.
✓ Branch 17 → 19 taken 4758 times.
5124 if (type == QLatin1String("member") || type == QLatin1String("member-of"))
205 366 continue;
206 // Holder side: the role in menu order ("Parent of" before "Child of"),
207 // stored as its inverse.
208
1/1
✓ Branch 19 → 20 taken 4758 times.
4758 const QString inverse = RelationVocabulary::inverseOf(type);
209
4/5
✓ Branch 20 → 21 taken 4680 times.
✓ Branch 20 → 24 taken 78 times.
✓ Branch 22 → 23 taken 4680 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 4758 times.
4758 append(holderSide && !inverse.isEmpty() ? inverse : type, category);
210 4758 }
211
2/2
✓ Branch 31 → 32 taken 720 times.
✓ Branch 31 → 49 taken 12 times.
732 if (holderSide) // types whose holder role is not editable itself (agent: "Assisted person of")
212
2/2
✓ Branch 47 → 34 taken 5040 times.
✓ Branch 47 → 48 taken 720 times.
5760 for (const QString &type : types)
213
6/6
✓ Branch 37 → 38 taken 4860 times.
✓ Branch 37 → 42 taken 180 times.
✓ Branch 40 → 41 taken 4680 times.
✓ Branch 40 → 42 taken 180 times.
✓ Branch 43 → 44 taken 4680 times.
✓ Branch 43 → 45 taken 360 times.
5040 if (type != QLatin1String("member") && type != QLatin1String("member-of"))
214
1/1
✓ Branch 44 → 45 taken 4680 times.
4680 append(type, category);
215 732 }
216 183 return out;
217 183 }
218
219 11 QString QuickContactActions::relationSentence(const QString &targetName, const QString &type,
220 const QString &holderName, const QString &targetGender,
221 const QString &custom) const {
222
7/11
✓ Branch 2 → 3 taken 11 times.
✓ Branch 4 → 5 taken 10 times.
✓ Branch 4 → 7 taken 1 time.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 10 times.
✓ Branch 9 → 10 taken 11 times.
✗ Branch 9 → 11 not taken.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 10 times.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
11 if (targetName.trimmed().isEmpty() || type.isEmpty())
223 1 return {};
224
2/2
✓ Branch 19 → 20 taken 10 times.
✓ Branch 20 → 21 taken 10 times.
20 return RelationVocabulary::sentence(targetName.trimmed(), RelationVocabulary::canonical(type),
225
3/5
✓ Branch 14 → 15 taken 10 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 10 times.
✗ Branch 17 → 19 not taken.
✓ Branch 18 → 19 taken 10 times.
20 holderName.trimmed().isEmpty() ? tr("this contact") : holderName.trimmed(),
226
2/2
✓ Branch 13 → 14 taken 10 times.
✓ Branch 21 → 22 taken 10 times.
30 targetGender, custom.trimmed());
227 }
228
229 413 QString QuickContactActions::relationHolderSentence(const QString &holderName, const QString &type,
230 const QString &targetName, const QString &custom) const {
231
2/2
✓ Branch 2 → 3 taken 413 times.
✓ Branch 3 → 4 taken 413 times.
413 const QString inverse = RelationVocabulary::inverseOf(RelationVocabulary::canonical(type));
232
8/11
✓ Branch 6 → 7 taken 404 times.
✓ Branch 6 → 10 taken 9 times.
✓ Branch 7 → 8 taken 404 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 404 times.
✓ Branch 12 → 13 taken 404 times.
✓ Branch 12 → 14 taken 9 times.
✓ Branch 14 → 15 taken 9 times.
✓ Branch 14 → 20 taken 404 times.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
413 if (inverse.isEmpty() || holderName.trimmed().isEmpty())
233
1/1
✓ Branch 16 → 17 taken 9 times.
9 return relationSentence(targetName, type, holderName, {}, custom);
234
2/3
✓ Branch 20 → 21 taken 404 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 404 times.
404 if (targetName.trimmed().isEmpty())
235 ✗ return {};
236
3/3
✓ Branch 27 → 28 taken 404 times.
✓ Branch 28 → 29 taken 404 times.
✓ Branch 29 → 30 taken 404 times.
404 return RelationVocabulary::sentence(holderName.trimmed(), inverse, targetName.trimmed());
237 413 }
238
239 42 QVariantMap QuickContactActions::parseLooseDate(const QString &text) const {
240
2/3
✓ Branch 2 → 3 taken 42 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 33 taken 42 times.
42 if (text.trimmed().isEmpty())
241 ✗ return {{QStringLiteral("ok"), true}, {QStringLiteral("iso"), QString()}, {QStringLiteral("text"), QString()}};
242
2/2
✓ Branch 33 → 34 taken 42 times.
✓ Branch 34 → 35 taken 42 times.
42 const LooseDate date = ContactEditorPeople::parseLooseDate(text);
243
3/3
✓ Branch 36 → 37 taken 42 times.
✓ Branch 37 → 38 taken 14 times.
✓ Branch 37 → 49 taken 28 times.
42 if (!date.isValid())
244
2/4
✓ Branch 44 → 45 taken 14 times.
✓ Branch 44 → 46 taken 14 times.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
56 return {{QStringLiteral("ok"), false}};
245 56 return {{QStringLiteral("ok"), true},
246
1/1
✓ Branch 56 → 57 taken 28 times.
56 {QStringLiteral("iso"), date.toIso()},
247
4/6
✓ Branch 61 → 62 taken 28 times.
✓ Branch 62 → 63 taken 28 times.
✓ Branch 66 → 67 taken 84 times.
✓ Branch 66 → 68 taken 28 times.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
168 {QStringLiteral("text"), MetSentence::formatDate(date)}};
248 182 }
249
250 26 QString QuickContactActions::looseDateText(const QString &iso) const {
251
3/3
✓ Branch 2 → 3 taken 26 times.
✓ Branch 3 → 4 taken 26 times.
✓ Branch 4 → 5 taken 26 times.
26 return MetSentence::formatDate(LooseDate::fromIso(iso));
252 }
253
254 48 QVariantMap QuickContactActions::recognizeSocial(const QString &text, const QString &kind) const {
255
1/1
✓ Branch 2 → 3 taken 48 times.
48 const ServiceCatalog &catalog = ServiceCatalog::builtIn();
256
2/2
✓ Branch 3 → 4 taken 48 times.
✓ Branch 4 → 5 taken 48 times.
48 const ServiceCatalog::Recognised found = catalog.recognise(text.trimmed(), kind);
257
1/1
✓ Branch 6 → 7 taken 48 times.
48 const ServiceCatalog::Service *service = catalog.find(found.service);
258 96 return {{QStringLiteral("recognised"), found.recognised},
259 96 {QStringLiteral("service"), found.service},
260 96 {QStringLiteral("kind"), found.kind},
261
1/1
✓ Branch 23 → 24 taken 48 times.
96 {QStringLiteral("label"), catalog.displayName(found.service)},
262 96 {QStringLiteral("handle"), found.handle},
263
1/3
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 48 times.
✗ Branch 34 → 36 not taken.
96 {QStringLiteral("value"), found.value.isEmpty() ? text.trimmed() : found.value},
264
4/8
✓ Branch 40 → 41 taken 48 times.
✗ Branch 40 → 43 not taken.
✓ Branch 41 → 42 taken 48 times.
✗ Branch 41 → 43 not taken.
✓ Branch 47 → 48 taken 336 times.
✓ Branch 47 → 49 taken 48 times.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
480 {QStringLiteral("glyph"), int(service && service->codepoint ? service->codepoint : 0xF0339)}};
265 432 }
266
267 161 QVariantList QuickContactActions::socialServices(const QString &kind) const {
268 161 const ServiceCatalog &catalog = ServiceCatalog::builtIn();
269 161 QVariantList out;
270
5/5
✓ Branch 3 → 4 taken 161 times.
✓ Branch 4 → 5 taken 161 times.
✓ Branch 5 → 6 taken 161 times.
✓ Branch 34 → 7 taken 1367 times.
✓ Branch 34 → 35 taken 161 times.
1528 for (const QString &id : catalog.idsForKind(kind))
271
4/6
✓ Branch 16 → 17 taken 1367 times.
✓ Branch 20 → 21 taken 1367 times.
✓ Branch 24 → 25 taken 2734 times.
✓ Branch 24 → 26 taken 1367 times.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
6996 out.append(QVariantMap{{QStringLiteral("id"), id}, {QStringLiteral("label"), catalog.displayName(id)}});
272 161 return out;
273 4101 }
274
275 1 QVariantList QuickContactActions::languageOptions(const QString &fragment) const {
276
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
1 const QString wanted = ContactIndex::foldForSort(fragment.trimmed());
277 1 QVariantList out;
278 1 QSet<int> seen;
279
5/6
✓ Branch 83 → 84 taken 345 times.
✓ Branch 83 → 87 taken 1 time.
✓ Branch 85 → 86 taken 345 times.
✗ Branch 85 → 87 not taken.
✓ Branch 88 → 7 taken 345 times.
✓ Branch 88 → 89 taken 1 time.
346 for (int value = int(QLocale::Abkhazian); value <= int(QLocale::LastLanguage) && out.size() < 8; ++value) {
280 345 const auto language = QLocale::Language(value);
281
1/1
✓ Branch 8 → 9 taken 345 times.
345 const QString code = QLocale::languageToCode(language, QLocale::ISO639Part1);
282
5/6
✓ Branch 10 → 11 taken 180 times.
✓ Branch 10 → 13 taken 165 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 180 times.
✓ Branch 15 → 16 taken 165 times.
✓ Branch 15 → 17 taken 180 times.
345 if (code.isEmpty() || seen.contains(value))
283 165 continue;
284 // The name in the UI language (QLocale names languages in English;
285 // the native name helps "Port" find Português).
286
1/1
✓ Branch 17 → 18 taken 180 times.
180 const QString english = QLocale::languageToString(language);
287
2/2
✓ Branch 18 → 19 taken 180 times.
✓ Branch 19 → 20 taken 180 times.
180 const QString native = QLocale(language).nativeLanguageName();
288
6/8
✓ Branch 23 → 24 taken 180 times.
✓ Branch 24 → 25 taken 180 times.
✓ Branch 25 → 26 taken 179 times.
✓ Branch 25 → 34 taken 1 time.
✓ Branch 43 → 44 taken 179 times.
✓ Branch 43 → 45 taken 1 time.
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 114 not taken.
360 if (!wanted.isEmpty() && !ContactIndex::foldForSort(english).startsWith(wanted) &&
289
14/24
✓ Branch 22 → 23 taken 180 times.
✗ Branch 22 → 34 not taken.
✓ Branch 26 → 27 taken 179 times.
✓ Branch 27 → 28 taken 179 times.
✓ Branch 28 → 29 taken 179 times.
✗ Branch 28 → 34 not taken.
✓ Branch 29 → 30 taken 179 times.
✓ Branch 30 → 31 taken 179 times.
✓ Branch 32 → 33 taken 179 times.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 36 taken 179 times.
✓ Branch 35 → 37 taken 1 time.
✓ Branch 37 → 38 taken 179 times.
✓ Branch 37 → 39 taken 1 time.
✓ Branch 39 → 40 taken 179 times.
✓ Branch 39 → 41 taken 1 time.
✓ Branch 41 → 42 taken 180 times.
✗ Branch 41 → 43 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 102 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
360 !ContactIndex::foldForSort(native).startsWith(wanted) && code != fragment.trimmed().toLower())
290 179 continue;
291
1/1
✓ Branch 45 → 46 taken 1 time.
1 seen.insert(value);
292
4/6
✓ Branch 53 → 54 taken 1 time.
✓ Branch 57 → 58 taken 1 time.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 61 → 63 taken 1 time.
✗ Branch 122 → 123 not taken.
✗ Branch 122 → 124 not taken.
5 out.append(QVariantMap{{QStringLiteral("code"), code}, {QStringLiteral("name"), languageName(code)}});
293 703 }
294 1 return out;
295 4 }
296
297 14 QString QuickContactActions::languageName(const QString &code) const { return ContactPeopleView::languageName(code); }
298
299 259 QVariantMap QuickContactActions::personEntry(const QString &key) const {
300
1/1
✓ Branch 2 → 3 taken 259 times.
259 ContactSyncCoordinator *sync = m_coordinator();
301 259 qint64 bookId = 0;
302 259 QString uid;
303
6/7
✓ Branch 3 → 4 taken 259 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 259 times.
✓ Branch 5 → 6 taken 42 times.
✓ Branch 5 → 7 taken 217 times.
✓ Branch 8 → 9 taken 42 times.
✓ Branch 8 → 10 taken 217 times.
259 if (!sync || !ContactQueries::splitKey(key, &bookId, &uid))
304 42 return {};
305
2/2
✓ Branch 11 → 12 taken 217 times.
✓ Branch 12 → 13 taken 217 times.
217 QSqlQuery q(sync->store()->database());
306
1/1
✓ Branch 17 → 18 taken 217 times.
217 q.prepare(QStringLiteral("SELECT c.display_name, c.has_photo, COALESCE(t.gender, ''), b.display_name,"
307 " (b.account_id = '' AND b.path = 'local'), c.kind"
308 " FROM cards c JOIN address_books b ON b.id = c.book_id "
309 "LEFT JOIN card_traits t ON t.card_id = c.id WHERE c.book_id = ? AND c.uid = ?"));
310
1/1
✓ Branch 22 → 23 taken 217 times.
217 q.addBindValue(bookId);
311
1/1
✓ Branch 26 → 27 taken 217 times.
217 q.addBindValue(uid);
312
7/8
✓ Branch 28 → 29 taken 217 times.
✓ Branch 29 → 30 taken 217 times.
✗ Branch 29 → 32 not taken.
✓ Branch 30 → 31 taken 217 times.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 33 taken 216 times.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 216 times.
217 if (!q.exec() || !q.next())
313 1 return {};
314
1/1
✓ Branch 36 → 37 taken 216 times.
216 ContactCard named;
315
2/2
✓ Branch 37 → 38 taken 216 times.
✓ Branch 38 → 39 taken 216 times.
216 named.formattedName = q.value(0).toString();
316 432 return {{QStringLiteral("key"), key},
317 432 {QStringLiteral("uid"), uid},
318 432 {QStringLiteral("name"), named.formattedName},
319 432 {QStringLiteral("address"), QString()},
320
9/11
✓ Branch 62 → 63 taken 216 times.
✓ Branch 63 → 64 taken 216 times.
✓ Branch 64 → 65 taken 14 times.
✓ Branch 64 → 66 taken 202 times.
✓ Branch 65 → 68 taken 14 times.
✓ Branch 66 → 67 taken 202 times.
✓ Branch 67 → 68 taken 202 times.
✓ Branch 120 → 121 taken 202 times.
✓ Branch 120 → 122 taken 14 times.
✗ Branch 218 → 219 not taken.
✗ Branch 218 → 220 not taken.
432 {QStringLiteral("book"), q.value(4).toBool() ? tr("On this computer") : q.value(3).toString()},
321
1/1
✓ Branch 72 → 73 taken 216 times.
432 {QStringLiteral("initials"), ContactIndex::initials(named)},
322
1/1
✓ Branch 77 → 78 taken 216 times.
432 {QStringLiteral("avatarColor"), ContactIndex::avatarColorIndex(uid)},
323
2/2
✓ Branch 82 → 83 taken 216 times.
✓ Branch 83 → 84 taken 216 times.
432 {QStringLiteral("hasPhoto"), q.value(1).toBool()},
324
2/2
✓ Branch 88 → 89 taken 216 times.
✓ Branch 89 → 90 taken 216 times.
432 {QStringLiteral("gender"), q.value(2).toString()},
325
4/6
✓ Branch 95 → 96 taken 216 times.
✓ Branch 96 → 97 taken 216 times.
✓ Branch 101 → 102 taken 2160 times.
✓ Branch 101 → 103 taken 216 times.
✗ Branch 161 → 162 not taken.
✗ Branch 161 → 163 not taken.
2592 {QStringLiteral("family"), q.value(5).toString() == QLatin1String("family")}};
326 2635 }
327
328 70 QVariantList QuickContactActions::searchPeople(const QString &fragment, const QString &exceptKey) const {
329
1/1
✓ Branch 2 → 3 taken 70 times.
70 ContactSyncCoordinator *sync = m_coordinator();
330
3/3
✓ Branch 3 → 4 taken 70 times.
✓ Branch 4 → 5 taken 70 times.
✓ Branch 5 → 6 taken 70 times.
70 const QString wanted = ContactIndex::foldForSort(fragment.left(64).trimmed());
331
5/6
✓ Branch 8 → 9 taken 70 times.
✗ Branch 8 → 11 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 69 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 69 times.
70 if (!sync || wanted.isEmpty())
332 1 return {};
333
1/1
✓ Branch 16 → 17 taken 69 times.
69 QSqlDatabase db = sync->store()->database();
334
3/3
✓ Branch 19 → 20 taken 69 times.
✓ Branch 20 → 21 taken 69 times.
✓ Branch 21 → 22 taken 69 times.
69 QList<ContactQueries::Row> rows = ContactQueries::page(db, {}, fragment.left(64), 60);
335 // The search text also holds relation and place names: keep the cards
336 // whose own name or address starts a word with the input (§4).
337
1/1
✓ Branch 30 → 31 taken 69 times.
69 const QString firstWord = wanted.section(QLatin1Char(' '), 0, 0);
338
1/1
✓ Branch 31 → 32 taken 69 times.
69 rows.removeIf([&firstWord, &exceptKey](const ContactQueries::Row &row) {
339
5/6
✓ Branch 3 → 4 taken 150 times.
✓ Branch 3 → 7 taken 1 time.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 150 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 150 times.
151 if (row.key == exceptKey || row.kind == QLatin1String("group"))
340 1 return true;
341
3/3
✓ Branch 13 → 14 taken 150 times.
✓ Branch 14 → 15 taken 150 times.
✓ Branch 15 → 16 taken 150 times.
450 for (const QString &word : ContactIndex::foldForSort(row.displayName + QLatin1Char(' ') + row.primaryAddress)
342
6/6
✓ Branch 21 → 22 taken 150 times.
✓ Branch 22 → 23 taken 150 times.
✓ Branch 29 → 30 taken 150 times.
✓ Branch 30 → 31 taken 150 times.
✓ Branch 38 → 32 taken 513 times.
✓ Branch 38 → 39 taken 74 times.
1037 .split(QRegularExpression(QStringLiteral("[\\s@.]+")), Qt::SkipEmptyParts))
343
3/3
✓ Branch 33 → 34 taken 513 times.
✓ Branch 34 → 35 taken 76 times.
✓ Branch 34 → 36 taken 437 times.
513 if (word.startsWith(firstWord))
344
2/2
✓ Branch 41 → 42 taken 74 times.
✓ Branch 41 → 44 taken 76 times.
150 return false;
345 74 return true;
346 });
347
3/3
✓ Branch 32 → 33 taken 69 times.
✓ Branch 33 → 34 taken 69 times.
✓ Branch 34 → 35 taken 69 times.
69 std::stable_sort(rows.begin(), rows.end(), [](const ContactQueries::Row &a, const ContactQueries::Row &b) {
348 22 return a.useCount > b.useCount;
349 });
350 69 QVariantList out;
351
1/1
✓ Branch 35 → 36 taken 69 times.
69 const QString me = meKey();
352
5/5
✓ Branch 36 → 37 taken 69 times.
✓ Branch 37 → 38 taken 69 times.
✓ Branch 38 → 39 taken 69 times.
✓ Branch 66 → 40 taken 76 times.
✓ Branch 66 → 67 taken 69 times.
145 for (const ContactQueries::Row &row : rows.mid(0, 8))
353
2/2
✓ Branch 42 → 43 taken 75 times.
✓ Branch 42 → 64 taken 1 time.
76 if (row.key != me) {
354
1/1
✓ Branch 43 → 44 taken 75 times.
75 QVariantMap entry = personEntry(row.key);
355
2/3
✓ Branch 44 → 45 taken 75 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 75 times.
75 if (entry.isEmpty()) continue; // S106-271: removed meanwhile (F103-19 guard)
356
1/1
✓ Branch 51 → 52 taken 75 times.
150 entry.insert(QStringLiteral("address"), row.primaryAddress);
357
1/1
✓ Branch 56 → 57 taken 75 times.
75 out.append(entry);
358 144 }
359 // D-6: "me" / "ich" (and the user's own name) find the me card first.
360
4/4
✓ Branch 70 → 71 taken 68 times.
✓ Branch 70 → 74 taken 1 time.
✓ Branch 73 → 74 taken 1 time.
✓ Branch 73 → 75 taken 67 times.
69 const bool asksForMe = wanted == QLatin1String("me") || wanted == QLatin1String("ich");
361
5/6
✓ Branch 77 → 78 taken 3 times.
✓ Branch 77 → 81 taken 66 times.
✓ Branch 79 → 80 taken 3 times.
✗ Branch 79 → 81 not taken.
✓ Branch 82 → 83 taken 3 times.
✓ Branch 82 → 102 taken 66 times.
69 if (!me.isEmpty() && me != exceptKey) {
362
1/1
✓ Branch 85 → 86 taken 3 times.
5 const bool named = std::any_of(rows.cbegin(), rows.cend(), [&me](const auto &row) { return row.key == me; });
363
4/4
✓ Branch 86 → 87 taken 2 times.
✓ Branch 86 → 88 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
✓ Branch 87 → 190 taken 1 time.
3 if (asksForMe || named) {
364
1/1
✓ Branch 88 → 89 taken 2 times.
2 QVariantMap entry = personEntry(me);
365
1/1
✓ Branch 93 → 94 taken 2 times.
4 entry.insert(QStringLiteral("isMe"), true);
366
1/1
✓ Branch 98 → 99 taken 2 times.
2 out.prepend(entry);
367 2 }
368
5/6
✓ Branch 103 → 104 taken 66 times.
✗ Branch 103 → 106 not taken.
✓ Branch 104 → 105 taken 1 time.
✓ Branch 104 → 106 taken 65 times.
✓ Branch 107 → 108 taken 1 time.
✓ Branch 107 → 190 taken 65 times.
66 } else if (me.isEmpty() && asksForMe) {
369 // "Is this you?" — the cards with an account address.
370
5/5
✓ Branch 108 → 109 taken 1 time.
✓ Branch 109 → 110 taken 1 time.
✓ Branch 110 → 111 taken 1 time.
✓ Branch 187 → 112 taken 1 time.
✓ Branch 187 → 188 taken 1 time.
2 for (const QVariant &value : meSuggestions()) {
371
4/4
✓ Branch 113 → 114 taken 1 time.
✓ Branch 118 → 119 taken 1 time.
✓ Branch 119 → 120 taken 1 time.
✓ Branch 120 → 121 taken 1 time.
2 QVariantMap entry = personEntry(value.toMap().value(QStringLiteral("key")).toString());
372
11/29
✓ Branch 127 → 128 taken 1 time.
✓ Branch 128 → 129 taken 1 time.
✗ Branch 128 → 137 not taken.
✓ Branch 134 → 135 taken 1 time.
✓ Branch 135 → 136 taken 1 time.
✗ Branch 136 → 137 not taken.
✓ Branch 136 → 138 taken 1 time.
✓ Branch 139 → 140 taken 1 time.
✗ Branch 139 → 141 not taken.
✓ Branch 141 → 142 taken 1 time.
✗ Branch 141 → 143 not taken.
✓ Branch 143 → 144 taken 1 time.
✗ Branch 143 → 145 not taken.
✓ Branch 145 → 146 taken 1 time.
✗ Branch 145 → 147 not taken.
✓ Branch 147 → 148 taken 1 time.
✗ Branch 147 → 149 not taken.
✗ Branch 149 → 150 not taken.
✓ Branch 149 → 151 taken 1 time.
✗ Branch 275 → 276 not taken.
✗ Branch 275 → 277 not taken.
✗ Branch 279 → 280 not taken.
✗ Branch 279 → 281 not taken.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 285 not taken.
✗ Branch 287 → 288 not taken.
✗ Branch 287 → 289 not taken.
✗ Branch 291 → 292 not taken.
✗ Branch 291 → 293 not taken.
2 if (entry.isEmpty() || entry.value(QStringLiteral("key")) == exceptKey)
373 ✗ continue;
374
3/3
✓ Branch 151 → 152 taken 1 time.
✓ Branch 156 → 157 taken 1 time.
✓ Branch 160 → 161 taken 1 time.
3 entry.insert(QStringLiteral("address"), value.toMap().value(QStringLiteral("address")));
375
1/1
✓ Branch 172 → 173 taken 1 time.
2 entry.insert(QStringLiteral("suggestMe"), true);
376
1/1
✓ Branch 177 → 178 taken 1 time.
1 out.prepend(entry);
377 2 }
378 }
379
1/1
✓ Branch 190 → 191 taken 69 times.
69 return out.mid(0, 9);
380 70 }
381
382 15 QVariantList QuickContactActions::familyNames(const QString &fragment, const QString &exceptKey) const {
383
1/1
✓ Branch 2 → 3 taken 15 times.
15 ContactSyncCoordinator *sync = m_coordinator();
384
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 15 times.
15 if (!sync)
385 ✗ return {};
386
1/1
✓ Branch 6 → 7 taken 15 times.
15 const QSqlDatabase db = sync->store()->database();
387 15 QHash<qint64, int> cards; // family card id → member count
388 15 QMap<QString, QPair<QString, int>> named; // folded name → name, member count
389
5/5
✓ Branch 7 → 8 taken 15 times.
✓ Branch 8 → 9 taken 15 times.
✓ Branch 9 → 10 taken 15 times.
✓ Branch 51 → 11 taken 386 times.
✓ Branch 51 → 52 taken 15 times.
401 for (const RelationIndex::Edge &edge : RelationIndex::allEdges(db)) {
390
2/2
✓ Branch 14 → 15 taken 43 times.
✓ Branch 14 → 16 taken 343 times.
386 if (edge.role != QLatin1String("relation"))
391 43 continue;
392
2/2
✓ Branch 18 → 19 taken 75 times.
✓ Branch 18 → 21 taken 268 times.
343 if (edge.type == QLatin1String("member")) {
393
1/1
✓ Branch 19 → 20 taken 75 times.
75 cards[edge.fromCardId] += 1;
394
5/6
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 26 taken 266 times.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 30 taken 266 times.
268 } else if (edge.type == QLatin1String("member-of") && edge.toCardId > 0) {
395
1/1
✓ Branch 28 → 29 taken 2 times.
2 cards[edge.toCardId] += 1;
396
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 49 taken 266 times.
266 } else if (edge.type == QLatin1String("member-of")) {
397 ✗ const QString name = edge.toName.simplified();
398 ✗ if (name.isEmpty())
399 ✗ continue;
400 ✗ QPair<QString, int> &entry = named[name.toCaseFolded()];
401 ✗ if (entry.first.isEmpty())
402 ✗ entry.first = name;
403 ✗ entry.second += 1;
404 ✗ }
405 15 }
406 // Every family's entry, also one without members yet (created on the spot, members removed).
407
1/1
✓ Branch 53 → 54 taken 15 times.
15 QSqlQuery entries(db);
408
2/3
✓ Branch 57 → 58 taken 15 times.
✓ Branch 60 → 61 taken 15 times.
✗ Branch 60 → 73 not taken.
15 if (entries.exec(QStringLiteral("SELECT id FROM cards WHERE kind = 'family'")))
409
3/3
✓ Branch 71 → 72 taken 38 times.
✓ Branch 72 → 62 taken 23 times.
✓ Branch 72 → 73 taken 15 times.
38 while (entries.next())
410
5/5
✓ Branch 62 → 63 taken 23 times.
✓ Branch 63 → 64 taken 23 times.
✓ Branch 65 → 66 taken 23 times.
✓ Branch 66 → 67 taken 23 times.
✓ Branch 67 → 68 taken 23 times.
23 cards.insert(entries.value(0).toLongLong(), cards.value(entries.value(0).toLongLong()));
411
3/3
✓ Branch 73 → 74 taken 15 times.
✓ Branch 74 → 75 taken 15 times.
✓ Branch 75 → 76 taken 15 times.
15 const QString wanted = fragment.left(64).simplified().toCaseFolded();
412
2/2
✓ Branch 2 → 3 taken 36 times.
✓ Branch 3 → 4 taken 36 times.
36 const auto matches = [&wanted](const QString &name) { return name.toCaseFolded().contains(wanted); };
413 15 QList<QVariantMap> out;
414 15 QSet<QString> cardNames;
415
2/2
✓ Branch 172 → 80 taken 37 times.
✓ Branch 172 → 173 taken 15 times.
52 for (auto it = cards.cbegin(); it != cards.cend(); ++it) {
416
1/1
✓ Branch 80 → 81 taken 37 times.
37 QSqlQuery q(db);
417
1/1
✓ Branch 84 → 85 taken 37 times.
37 q.prepare(QStringLiteral("SELECT book_id, uid FROM cards WHERE id = ?"));
418
1/1
✓ Branch 90 → 91 taken 37 times.
37 q.addBindValue(it.key());
419
5/8
✓ Branch 92 → 93 taken 37 times.
✓ Branch 93 → 94 taken 37 times.
✗ Branch 93 → 96 not taken.
✓ Branch 94 → 95 taken 37 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 37 times.
✗ Branch 98 → 99 not taken.
✓ Branch 98 → 100 taken 37 times.
37 if (!q.exec() || !q.next())
420 ✗ continue;
421
5/5
✓ Branch 100 → 101 taken 37 times.
✓ Branch 101 → 102 taken 37 times.
✓ Branch 102 → 103 taken 37 times.
✓ Branch 103 → 104 taken 37 times.
✓ Branch 104 → 105 taken 37 times.
37 const QString key = ContactQueries::cardKey(q.value(0).toLongLong(), q.value(1).toString());
422
1/1
✓ Branch 108 → 109 taken 37 times.
37 QVariantMap entry = personEntry(key);
423
2/2
✓ Branch 113 → 114 taken 37 times.
✓ Branch 114 → 115 taken 37 times.
74 const QString name = entry.value(QStringLiteral("name")).toString();
424
6/7
✓ Branch 119 → 120 taken 37 times.
✓ Branch 120 → 121 taken 37 times.
✗ Branch 120 → 123 not taken.
✓ Branch 122 → 123 taken 1 time.
✓ Branch 122 → 124 taken 36 times.
✓ Branch 125 → 126 taken 1 time.
✓ Branch 125 → 127 taken 36 times.
37 if (entry.isEmpty() || key == exceptKey)
425 1 continue;
426
3/3
✓ Branch 127 → 128 taken 36 times.
✓ Branch 128 → 129 taken 36 times.
✓ Branch 129 → 130 taken 36 times.
36 cardNames.insert(name.simplified().toCaseFolded());
427
3/3
✓ Branch 132 → 133 taken 36 times.
✓ Branch 133 → 134 taken 16 times.
✓ Branch 133 → 135 taken 20 times.
36 if (!matches(name))
428 16 continue;
429 20 const int count = it.value();
430
6/6
✓ Branch 136 → 137 taken 1 time.
✓ Branch 136 → 138 taken 19 times.
✓ Branch 137 → 141 taken 1 time.
✓ Branch 139 → 141 taken 9 times.
✓ Branch 140 → 141 taken 10 times.
✓ Branch 145 → 146 taken 20 times.
59 entry.insert(QStringLiteral("detail"), count == 0 ? tr("No members yet")
431
2/2
✓ Branch 138 → 139 taken 9 times.
✓ Branch 138 → 140 taken 10 times.
19 : count == 1 ? tr("1 member")
432 : tr("%n members", "", count));
433
1/1
✓ Branch 150 → 151 taken 20 times.
20 out.append(entry);
434 88 }
435
3/4
✓ Branch 173 → 174 taken 15 times.
✓ Branch 250 → 251 taken 15 times.
✗ Branch 252 → 175 not taken.
✓ Branch 252 → 253 taken 15 times.
15 for (auto it = named.cbegin(); it != named.cend(); ++it) {
436 ✗ if (cardNames.contains(it.key()) || !matches(it.value().first))
437 ✗ continue;
438 ✗ ContactCard card;
439 ✗ card.formattedName = it.value().first;
440 ✗ out.append({{QStringLiteral("key"), QString()},
441 ✗ {QStringLiteral("name"), card.formattedName},
442 ✗ {QStringLiteral("textOnly"), true},
443 ✗ {QStringLiteral("initials"), ContactIndex::initials(card)},
444 ✗ {QStringLiteral("avatarColor"), ContactIndex::avatarColorIndex(card.formattedName)},
445 ✗ {QStringLiteral("hasPhoto"), false},
446 ✗ {QStringLiteral("detail"), it.value().second == 1 ? tr("1 member")
447 ✗ : tr("%n members", "", it.value().second)}});
448 ✗ }
449
3/3
✓ Branch 253 → 254 taken 15 times.
✓ Branch 254 → 255 taken 15 times.
✓ Branch 255 → 256 taken 15 times.
15 std::sort(out.begin(), out.end(), [](const QVariantMap &a, const QVariantMap &b) {
450
3/3
✓ Branch 12 → 13 taken 11 times.
✓ Branch 13 → 14 taken 11 times.
✓ Branch 14 → 15 taken 11 times.
33 return QString::localeAwareCompare(a.value(QStringLiteral("name")).toString(),
451
2/2
✓ Branch 6 → 7 taken 11 times.
✓ Branch 7 → 8 taken 11 times.
44 b.value(QStringLiteral("name")).toString()) < 0;
452 });
453 15 QVariantList list;
454
5/5
✓ Branch 256 → 257 taken 15 times.
✓ Branch 257 → 258 taken 15 times.
✓ Branch 258 → 259 taken 15 times.
✓ Branch 266 → 260 taken 20 times.
✓ Branch 266 → 267 taken 15 times.
35 for (const QVariantMap &entry : out.mid(0, 8))
455
1/1
✓ Branch 262 → 263 taken 20 times.
35 list.append(entry);
456
10/10
✓ Branch 268 → 269 taken 15 times.
✓ Branch 272 → 273 taken 15 times.
✓ Branch 273 → 274 taken 15 times.
✓ Branch 275 → 276 taken 15 times.
✓ Branch 276 → 277 taken 15 times.
✓ Branch 277 → 278 taken 15 times.
✓ Branch 278 → 279 taken 15 times.
✓ Branch 279 → 280 taken 15 times.
✓ Branch 283 → 270 taken 15 times.
✓ Branch 283 → 284 taken 15 times.
30 qCDebug(lcContactRelationActions) << "Family names:" << cards.size() << "entries," << named.size() << "by name";
457 15 return list;
458 15 }
459
460 54 QVariantList QuickContactActions::pickPeople(const QString &fragment, const QString &exceptKey) const {
461
1/1
✓ Branch 2 → 3 taken 54 times.
54 ContactSyncCoordinator *sync = m_coordinator();
462
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 54 times.
54 if (!sync)
463 ✗ return {};
464 54 QVariantList found;
465
3/3
✓ Branch 5 → 6 taken 54 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 8 → 13 taken 48 times.
54 if (!fragment.trimmed().isEmpty()) {
466
1/1
✓ Branch 9 → 10 taken 6 times.
6 found = searchPeople(fragment, exceptKey);
467 } else {
468 48 const ContactQueries::View frequent{ContactQueries::ViewKind::Frequent};
469 // F103-19: the frequent list also holds collected addresses, which are no
470 // cards (personEntry is empty — they showed as "undefined").
471
6/6
✓ Branch 17 → 18 taken 48 times.
✓ Branch 18 → 19 taken 48 times.
✓ Branch 23 → 24 taken 48 times.
✓ Branch 24 → 25 taken 48 times.
✓ Branch 48 → 26 taken 84 times.
✓ Branch 48 → 49 taken 48 times.
132 for (const ContactQueries::Row &row : ContactQueries::page(sync->store()->database(), frequent, QString(), 16))
472
4/8
✓ Branch 28 → 29 taken 84 times.
✗ Branch 28 → 35 not taken.
✓ Branch 31 → 32 taken 84 times.
✗ Branch 31 → 35 not taken.
✓ Branch 33 → 34 taken 84 times.
✗ Branch 33 → 35 not taken.
✓ Branch 36 → 37 taken 84 times.
✗ Branch 36 → 46 not taken.
84 if (row.key != exceptKey && row.kind != QLatin1String("group") && found.size() < 8)
473
4/4
✓ Branch 37 → 38 taken 84 times.
✓ Branch 38 → 39 taken 84 times.
✓ Branch 39 → 40 taken 42 times.
✓ Branch 39 → 44 taken 42 times.
84 if (const QVariantMap entry = personEntry(row.key); !entry.isEmpty())
474
1/1
✓ Branch 41 → 42 taken 42 times.
132 found.append(entry);
475 48 }
476 54 QVariantList mine;
477
1/1
✓ Branch 52 → 53 taken 54 times.
54 const QString me = meKey();
478
2/2
✓ Branch 54 → 55 taken 4 times.
✓ Branch 54 → 71 taken 50 times.
54 if (!me.isEmpty()) {
479
2/2
✓ Branch 56 → 57 taken 3 times.
✓ Branch 56 → 153 taken 1 time.
4 if (me != exceptKey) {
480
1/1
✓ Branch 57 → 58 taken 3 times.
3 QVariantMap entry = personEntry(me);
481
1/1
✓ Branch 62 → 63 taken 3 times.
6 entry.insert(QStringLiteral("isMe"), true);
482
1/1
✓ Branch 67 → 68 taken 3 times.
3 mine.append(entry);
483 3 }
484 } else {
485
5/5
✓ Branch 71 → 72 taken 50 times.
✓ Branch 72 → 73 taken 50 times.
✓ Branch 73 → 74 taken 50 times.
✓ Branch 150 → 75 taken 16 times.
✓ Branch 150 → 151 taken 50 times.
66 for (const QVariant &value : meSuggestions()) {
486
4/4
✓ Branch 76 → 77 taken 16 times.
✓ Branch 81 → 82 taken 16 times.
✓ Branch 82 → 83 taken 16 times.
✓ Branch 83 → 84 taken 16 times.
32 QVariantMap entry = personEntry(value.toMap().value(QStringLiteral("key")).toString());
487
11/29
✓ Branch 90 → 91 taken 16 times.
✓ Branch 91 → 92 taken 16 times.
✗ Branch 91 → 100 not taken.
✓ Branch 97 → 98 taken 16 times.
✓ Branch 98 → 99 taken 16 times.
✗ Branch 99 → 100 not taken.
✓ Branch 99 → 101 taken 16 times.
✓ Branch 102 → 103 taken 16 times.
✗ Branch 102 → 104 not taken.
✓ Branch 104 → 105 taken 16 times.
✗ Branch 104 → 106 not taken.
✓ Branch 106 → 107 taken 16 times.
✗ Branch 106 → 108 not taken.
✓ Branch 108 → 109 taken 16 times.
✗ Branch 108 → 110 not taken.
✓ Branch 110 → 111 taken 16 times.
✗ Branch 110 → 112 not taken.
✗ Branch 112 → 113 not taken.
✓ Branch 112 → 114 taken 16 times.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 272 not taken.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
✗ Branch 278 → 279 not taken.
✗ Branch 278 → 280 not taken.
✗ Branch 282 → 283 not taken.
✗ Branch 282 → 284 not taken.
32 if (entry.isEmpty() || entry.value(QStringLiteral("key")) == exceptKey)
488 ✗ continue;
489
3/3
✓ Branch 114 → 115 taken 16 times.
✓ Branch 119 → 120 taken 16 times.
✓ Branch 123 → 124 taken 16 times.
48 entry.insert(QStringLiteral("address"), value.toMap().value(QStringLiteral("address")));
490
1/1
✓ Branch 135 → 136 taken 16 times.
32 entry.insert(QStringLiteral("suggestMe"), true);
491
1/1
✓ Branch 140 → 141 taken 16 times.
16 mine.append(entry);
492 66 }
493 }
494 54 QSet<QString> first;
495
2/2
✓ Branch 174 → 157 taken 19 times.
✓ Branch 174 → 175 taken 54 times.
73 for (const QVariant &entry : std::as_const(mine))
496
4/4
✓ Branch 158 → 159 taken 19 times.
✓ Branch 163 → 164 taken 19 times.
✓ Branch 164 → 165 taken 19 times.
✓ Branch 165 → 166 taken 19 times.
38 first.insert(entry.toMap().value(QStringLiteral("key")).toString());
497
2/2
✓ Branch 197 → 178 taken 49 times.
✓ Branch 197 → 198 taken 54 times.
103 for (const QVariant &entry : std::as_const(found))
498
5/5
✓ Branch 179 → 180 taken 49 times.
✓ Branch 184 → 185 taken 49 times.
✓ Branch 185 → 186 taken 49 times.
✓ Branch 193 → 194 taken 48 times.
✓ Branch 193 → 195 taken 1 time.
98 if (!first.contains(entry.toMap().value(QStringLiteral("key")).toString()))
499
1/1
✓ Branch 194 → 195 taken 48 times.
48 mine.append(entry);
500 // People only: a family is no one's relative and no member of a family.
501
4/4
✓ Branch 2 → 3 taken 67 times.
✓ Branch 7 → 8 taken 67 times.
✓ Branch 8 → 9 taken 67 times.
✓ Branch 198 → 199 taken 54 times.
188 mine.removeIf([](const QVariant &entry) { return entry.toMap().value(QStringLiteral("family")).toBool(); });
502
1/1
✓ Branch 199 → 200 taken 54 times.
54 return mine.mid(0, 9);
503 54 }
504
505 2 QVariantMap QuickContactActions::createPerson(const QString &name) {
506
1/1
✓ Branch 2 → 3 taken 2 times.
2 const QString trimmed = name.simplified();
507
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1 time.
2 if (trimmed.isEmpty())
508 1 return {};
509 // "Rui Costa" → given "Rui", family "Costa"; one word is the given name.
510 1 const int split = int(trimmed.lastIndexOf(QLatin1Char(' ')));
511 2 const QVariantMap fields{{QStringLiteral("formattedName"), trimmed},
512
2/3
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
2 {QStringLiteral("given"), split > 0 ? trimmed.left(split) : trimmed},
513
4/7
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 25 not taken.
✓ Branch 24 → 26 taken 1 time.
✓ Branch 29 → 30 taken 3 times.
✓ Branch 29 → 31 taken 1 time.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 93 not taken.
5 {QStringLiteral("family"), split > 0 ? trimmed.mid(split + 1) : QString()}};
514
2/2
✓ Branch 39 → 40 taken 1 time.
✓ Branch 40 → 41 taken 1 time.
1 const QVariantMap created = create(defaultBookId(), fields, true);
515
3/4
✓ Branch 45 → 46 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 1 time.
2 if (!created.value(QStringLiteral("ok")).toBool())
516 ✗ return {};
517
2/2
✓ Branch 57 → 58 taken 1 time.
✓ Branch 58 → 59 taken 1 time.
2 const QString key = created.value(QStringLiteral("key")).toString();
518
1/1
✓ Branch 68 → 69 taken 1 time.
1 pushUndo(tr("Undo new contact"), {QStringLiteral("created"), {}, key, 0});
519
5/5
✓ Branch 72 → 73 taken 1 time.
✓ Branch 76 → 77 taken 1 time.
✓ Branch 77 → 78 taken 1 time.
✓ Branch 81 → 74 taken 1 time.
✓ Branch 81 → 82 taken 1 time.
2 qCInfo(lcContactRelationActions) << "Created a contact from the people picker";
520
1/1
✓ Branch 82 → 83 taken 1 time.
1 return personEntry(key);
521 8 }
522
523 // ─── me card (S103.1.6, D-6) ───
524
525 2241 QString QuickContactActions::meKey() const {
526
3/3
✓ Branch 2 → 3 taken 2241 times.
✓ Branch 7 → 8 taken 2241 times.
✓ Branch 8 → 9 taken 2241 times.
4482 const QString key = QSettings().value(QStringLiteral("contacts/meCard")).toString();
527
1/1
✓ Branch 13 → 14 taken 2241 times.
2241 ContactSyncCoordinator *sync = m_coordinator();
528 2241 qint64 bookId = 0;
529 2241 QString uid;
530 // A deleted me card is no longer "me"; undoing the delete brings it back.
531
12/16
✓ Branch 15 → 16 taken 71 times.
✓ Branch 15 → 23 taken 2170 times.
✓ Branch 16 → 17 taken 71 times.
✗ Branch 16 → 23 not taken.
✓ Branch 17 → 18 taken 71 times.
✓ Branch 18 → 19 taken 71 times.
✗ Branch 18 → 23 not taken.
✓ Branch 20 → 21 taken 71 times.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 24 taken 70 times.
✓ Branch 25 → 26 taken 71 times.
✓ Branch 25 → 27 taken 2170 times.
✓ Branch 27 → 28 taken 2171 times.
✓ Branch 27 → 29 taken 70 times.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
2241 if (key.isEmpty() || !sync || !ContactQueries::splitKey(key, &bookId, &uid) || !sync->store()->card(bookId, uid))
532 2171 return {};
533 70 return key;
534 2241 }
535
536 6 bool QuickContactActions::setMe(const QString &key) {
537
4/4
✓ Branch 2 → 3 taken 6 times.
✓ Branch 3 → 4 taken 6 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 5 times.
6 if (personEntry(key).isEmpty())
538 1 return false;
539
2/2
✓ Branch 7 → 8 taken 5 times.
✓ Branch 13 → 14 taken 5 times.
10 QSettings().setValue(QStringLiteral("contacts/meCard"), key);
540
5/5
✓ Branch 18 → 19 taken 5 times.
✓ Branch 22 → 23 taken 5 times.
✓ Branch 23 → 24 taken 5 times.
✓ Branch 27 → 20 taken 5 times.
✓ Branch 27 → 28 taken 5 times.
10 qCInfo(lcContactRelationActions) << "Me card set";
541 5 emit meChanged();
542
2/2
✓ Branch 29 → 30 taken 5 times.
✓ Branch 30 → 31 taken 5 times.
5 report(tr("Marked as you"), false);
543 5 return true;
544 }
545
546 4 void QuickContactActions::clearMe() {
547
2/2
✓ Branch 2 → 3 taken 4 times.
✓ Branch 7 → 8 taken 4 times.
8 QSettings().remove(QStringLiteral("contacts/meCard"));
548
5/5
✓ Branch 11 → 12 taken 4 times.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 20 → 13 taken 4 times.
✓ Branch 20 → 21 taken 4 times.
8 qCInfo(lcContactRelationActions) << "Me card cleared";
549 4 emit meChanged();
550 4 }
551
552 109 QVariantList QuickContactActions::meSuggestions() const {
553
1/1
✓ Branch 2 → 3 taken 109 times.
109 ContactSyncCoordinator *sync = m_coordinator();
554
5/6
✓ Branch 3 → 4 taken 109 times.
✗ Branch 3 → 6 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 108 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 108 times.
109 if (!sync || !m_ownAddresses)
555 1 return {};
556 108 QVariantList out;
557 108 QStringList seen;
558
5/5
✓ Branch 10 → 11 taken 108 times.
✓ Branch 11 → 12 taken 108 times.
✓ Branch 12 → 13 taken 108 times.
✓ Branch 61 → 14 taken 108 times.
✓ Branch 61 → 62 taken 108 times.
216 for (const QString &address : m_ownAddresses()) {
559
2/2
✓ Branch 16 → 17 taken 108 times.
✓ Branch 17 → 18 taken 108 times.
108 const ContactQueries::Lookup found = ContactQueries::lookup(sync->store()->database(), address);
560
5/6
✓ Branch 19 → 20 taken 37 times.
✓ Branch 19 → 22 taken 71 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 37 times.
✓ Branch 24 → 25 taken 71 times.
✓ Branch 24 → 26 taken 37 times.
108 if (!found.known || seen.contains(found.key))
561 71 continue;
562
1/1
✓ Branch 26 → 27 taken 37 times.
37 seen.append(found.key);
563
1/1
✓ Branch 41 → 42 taken 37 times.
111 out.append(QVariantMap{{QStringLiteral("key"), found.key},
564 74 {QStringLiteral("name"), found.displayName},
565
2/4
✓ Branch 45 → 46 taken 111 times.
✓ Branch 45 → 47 taken 37 times.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
185 {QStringLiteral("address"), address}});
566 216 }
567 108 return out;
568 256 }
569