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: 98.0% 291 / 0 / 297
Functions: 100.0% 25 / 0 / 25
Branches: 88.3% 436 / 0 / 494

data/VCardPeople.cpp
Line Branch Exec Source
1 // Read side of the Sprint 103 people details (S103.1.1–S103.1.3, S103.1.11):
2 // relationships, how we met, social profiles and chat, languages,
3 // interests, other dates, birthplace, positions, time zone, gender and mail
4 // links. Encodings: SPRINT-103.md D-3, D-4, D-7, D-8, D-9, D-10, D-15.
5 #include "RelationVocabulary.h"
6 #include "VCardCodec.h"
7 #include "VCardWriter.h"
8
9 #include <QUrl>
10
11 namespace VCardCodec {
12
13 namespace {
14
15 51358 QString text(const ICal::Property &property) {
16
2/2
✓ Branch 2 → 3 taken 51358 times.
✓ Branch 3 → 4 taken 51358 times.
51358 QString value = ICal::unescapeText(decodedValue(property));
17
1/1
✓ Branch 11 → 12 taken 51358 times.
102716 value.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
18
1/1
✓ Branch 16 → 17 taken 51358 times.
102716 return value.trimmed();
19 51358 }
20
21 34900 QString groupLabel(const ICal::Component &card, const ICal::Property &property) {
22
1/1
✓ Branch 2 → 3 taken 34900 times.
34900 const QString group = groupOf(property);
23
2/2
✓ Branch 4 → 5 taken 30195 times.
✓ Branch 4 → 6 taken 4705 times.
34900 if (group.isEmpty())
24 30195 return {};
25
2/2
✓ Branch 43 → 8 taken 62815 times.
✓ Branch 43 → 44 taken 87 times.
62902 for (const ICal::Property &candidate : card.properties) {
26
11/16
✓ Branch 9 → 10 taken 62815 times.
✓ Branch 11 → 12 taken 9373 times.
✓ Branch 11 → 16 taken 53442 times.
✓ Branch 13 → 14 taken 9373 times.
✓ Branch 15 → 16 taken 4755 times.
✓ Branch 15 → 17 taken 4618 times.
✓ Branch 18 → 19 taken 9373 times.
✓ Branch 18 → 20 taken 53442 times.
✓ Branch 20 → 21 taken 62815 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 58197 times.
✓ Branch 22 → 24 taken 4618 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
62815 if (groupOf(candidate) != group || baseName(candidate) != QLatin1String("X-ABLABEL"))
27 58197 continue;
28
1/1
✓ Branch 24 → 25 taken 4618 times.
4618 QString label = text(candidate);
29
7/8
✓ Branch 26 → 27 taken 4618 times.
✓ Branch 27 → 28 taken 363 times.
✓ Branch 27 → 32 taken 4255 times.
✓ Branch 29 → 30 taken 363 times.
✓ Branch 30 → 31 taken 363 times.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 363 times.
✓ Branch 33 → 39 taken 4255 times.
4618 if (label.startsWith(QLatin1String("_$!<")) && label.endsWith(QLatin1String(">!$_")))
30
1/1
✓ Branch 35 → 36 taken 363 times.
363 label = label.mid(4, label.size() - 8);
31 4618 return label;
32 4618 }
33 87 return {};
34 34900 }
35
36 // First TYPE value that is not "pref" (RELATED;TYPE=friend,colleague → friend).
37 33811 QString firstType(const ICal::Property &property) {
38
2/2
✓ Branch 45 → 4 taken 33986 times.
✓ Branch 45 → 46 taken 43 times.
34029 for (const ICal::Parameter &parameter : property.parameters) {
39
2/2
✓ Branch 7 → 8 taken 210 times.
✓ Branch 7 → 9 taken 33776 times.
33986 if (parameter.name != QLatin1String("TYPE"))
40 210 continue;
41
5/5
✓ Branch 12 → 13 taken 33776 times.
✓ Branch 13 → 14 taken 33776 times.
✓ Branch 14 → 15 taken 33776 times.
✓ Branch 36 → 16 taken 33768 times.
✓ Branch 36 → 37 taken 8 times.
33776 for (const QString &word : parameter.value.split(QLatin1Char(','), Qt::SkipEmptyParts)) {
42
2/2
✓ Branch 17 → 18 taken 33768 times.
✓ Branch 18 → 19 taken 33768 times.
33768 const QString lower = word.trimmed().toLower();
43
3/6
✓ Branch 21 → 22 taken 33768 times.
✗ Branch 21 → 26 not taken.
✓ Branch 24 → 25 taken 33768 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 33768 times.
✗ Branch 27 → 29 not taken.
33768 if (!lower.isEmpty() && lower != QLatin1String("pref"))
44 33768 return lower;
45 33776 }
46 }
47 43 return {};
48 }
49
50 31651 bool isTextValue(const ICal::Property &property) {
51
1/1
✓ Branch 5 → 6 taken 31651 times.
31651 return property.parameter(QStringLiteral("VALUE")).compare(QLatin1String("text"), Qt::CaseInsensitive) == 0;
52 }
53
54 // The person a RELATED/X-ABRELATEDNAMES line points to.
55 32182 ContactPersonRef targetOf(const ICal::Property &property, bool apple, int index) {
56 32182 ContactPersonRef ref;
57 32182 ref.sourceIndex = index;
58
1/1
✓ Branch 2 → 3 taken 32182 times.
32182 const QString value = text(property);
59
1/1
✓ Branch 6 → 7 taken 32182 times.
32182 ref.uid = property.parameter(QStringLiteral("X-MAILJD-UID"));
60
7/7
✓ Branch 11 → 12 taken 31651 times.
✓ Branch 11 → 14 taken 531 times.
✓ Branch 12 → 13 taken 31651 times.
✓ Branch 13 → 14 taken 27108 times.
✓ Branch 13 → 15 taken 4543 times.
✓ Branch 16 → 17 taken 27639 times.
✓ Branch 16 → 19 taken 4543 times.
32182 if (apple || isTextValue(property)) {
61 27639 ref.name = value;
62 27639 return ref;
63 }
64 4543 ref.uri = value;
65
6/7
✓ Branch 21 → 22 taken 4543 times.
✓ Branch 22 → 23 taken 4323 times.
✓ Branch 22 → 26 taken 220 times.
✓ Branch 24 → 25 taken 4323 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 4323 times.
✓ Branch 27 → 32 taken 220 times.
4543 if (value.startsWith(QLatin1String("urn:uuid:"), Qt::CaseInsensitive) && ref.uid.isEmpty())
66
1/1
✓ Branch 28 → 29 taken 4323 times.
4323 ref.uid = value.mid(9);
67 4543 return ref;
68 32182 }
69
70 1712 PartialDate labelledDate(const ICal::Property &property) {
71
1/1
✓ Branch 2 → 3 taken 1712 times.
1712 QString value = text(property);
72 1712 const int time = value.indexOf(QLatin1Char('T'));
73
2/2
✓ Branch 6 → 7 taken 4 times.
✓ Branch 6 → 11 taken 1708 times.
1712 if (time > 0)
74
1/1
✓ Branch 7 → 8 taken 4 times.
4 value = value.left(time);
75
1/1
✓ Branch 11 → 12 taken 1712 times.
1712 PartialDate date = PartialDate::fromIso(value);
76
3/3
✓ Branch 15 → 16 taken 1712 times.
✓ Branch 18 → 19 taken 508 times.
✓ Branch 18 → 20 taken 1204 times.
1712 if (property.hasParameter(QStringLiteral("X-APPLE-OMIT-YEAR")))
77 508 date.year = 0;
78 1712 return date;
79 1712 }
80
81 33188 bool isIntroducer(const ICal::Property &property, const QString &type, const QString &label) {
82
3/11
✓ Branch 5 → 6 taken 33188 times.
✓ Branch 21 → 22 taken 33188 times.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 33188 times.
✗ Branch 23 → 25 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
99564 return property.parameter(QStringLiteral("X-MAILJD-ROLE")).compare(QLatin1String("met-via"),
83 32163 Qt::CaseInsensitive) == 0 ||
84
8/9
✓ Branch 8 → 9 taken 32163 times.
✓ Branch 8 → 17 taken 1025 times.
✓ Branch 9 → 10 taken 32163 times.
✓ Branch 10 → 11 taken 31989 times.
✓ Branch 10 → 17 taken 174 times.
✓ Branch 13 → 14 taken 31940 times.
✓ Branch 13 → 17 taken 49 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 31940 times.
131504 RelationVocabulary::isMetViaLabel(label) || type == QLatin1String("x-referred-by") ||
85
1/2
✓ Branch 19 → 20 taken 33188 times.
✗ Branch 19 → 21 not taken.
98316 type == QLatin1String("referred-by");
86 }
87
88 // RELATED (D-3) and Apple's X-ABRELATEDNAMES; introducers go to met.via (D-7).
89 33188 void readRelation(const ICal::Component &tree, int index, ContactCard *card) {
90 33188 const ICal::Property &property = tree.properties.at(index);
91
1/1
✓ Branch 4 → 5 taken 33188 times.
33188 const bool apple = baseName(property) == QLatin1String("X-ABRELATEDNAMES");
92
1/1
✓ Branch 7 → 8 taken 33188 times.
33188 const QString label = groupLabel(tree, property);
93
3/3
✓ Branch 8 → 9 taken 531 times.
✓ Branch 8 → 10 taken 32657 times.
✓ Branch 10 → 11 taken 32657 times.
33188 const QString rawType = apple ? QString() : firstType(property);
94
3/3
✓ Branch 11 → 12 taken 33188 times.
✓ Branch 12 → 13 taken 1248 times.
✓ Branch 12 → 17 taken 31940 times.
33188 if (isIntroducer(property, rawType, label)) {
95
2/2
✓ Branch 13 → 14 taken 1248 times.
✓ Branch 14 → 15 taken 1248 times.
1248 card->met.via.append(targetOf(property, apple, index));
96 1248 return;
97 }
98
2/2
✓ Branch 18 → 19 taken 1006 times.
✓ Branch 18 → 20 taken 30934 times.
31940 if (card->relations.size() >= MaxRelations)
99 1006 return;
100
1/1
✓ Branch 20 → 21 taken 30934 times.
30934 const ContactPersonRef target = targetOf(property, apple, index);
101 30934 ContactRelation relation;
102 30934 relation.targetUri = target.uri;
103 30934 relation.targetUid = target.uid;
104 30934 relation.targetName = target.name;
105
6/6
✓ Branch 24 → 25 taken 433 times.
✓ Branch 24 → 28 taken 30501 times.
✓ Branch 33 → 34 taken 30501 times.
✓ Branch 33 → 35 taken 433 times.
✓ Branch 35 → 36 taken 433 times.
✓ Branch 35 → 37 taken 30501 times.
61868 relation.model = apple ? QStringLiteral("apple") : QStringLiteral("related");
106 30934 relation.sourceIndex = index;
107
2/2
✓ Branch 40 → 41 taken 30934 times.
✓ Branch 41 → 42 taken 30934 times.
30934 const QString kin = property.parameter(QStringLiteral("X-MAILJD-KIN")).toLower();
108
5/6
✓ Branch 47 → 48 taken 2 times.
✓ Branch 47 → 51 taken 30932 times.
✓ Branch 49 → 50 taken 2 times.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 52 → 55 taken 30932 times.
30934 if (kin == QLatin1String("custom") && !label.isEmpty()) {
109 2 relation.type = kin; // S106-172: a custom label that is also a known word
110 2 relation.label = label;
111
2/2
✓ Branch 56 → 57 taken 1200 times.
✓ Branch 56 → 58 taken 29732 times.
30932 } else if (!kin.isEmpty()) {
112 1200 relation.type = kin;
113
10/10
✓ Branch 58 → 59 taken 29300 times.
✓ Branch 58 → 69 taken 432 times.
✓ Branch 60 → 61 taken 29267 times.
✓ Branch 60 → 67 taken 33 times.
✓ Branch 63 → 64 taken 28863 times.
✓ Branch 63 → 67 taken 404 times.
✓ Branch 66 → 67 taken 120 times.
✓ Branch 66 → 70 taken 28743 times.
✓ Branch 71 → 72 taken 743 times.
✓ Branch 71 → 105 taken 28989 times.
30289 } else if (apple || ((rawType.isEmpty() || rawType == QLatin1String("kin") || rawType == QLatin1String("contact")) &&
114
2/2
✓ Branch 68 → 69 taken 311 times.
✓ Branch 68 → 70 taken 246 times.
557 !label.isEmpty())) {
115 // A labelled relation: the label names the type ("Großelternteil",
116 // "_$!<Mother>!$_"); an unknown label is a custom type.
117
1/1
✓ Branch 72 → 73 taken 743 times.
743 relation.type = RelationVocabulary::typeForLabel(label);
118
7/8
✓ Branch 76 → 77 taken 192 times.
✓ Branch 76 → 82 taken 551 times.
✓ Branch 79 → 80 taken 6 times.
✓ Branch 79 → 82 taken 186 times.
✓ Branch 80 → 81 taken 6 times.
✗ Branch 80 → 82 not taken.
✓ Branch 83 → 84 taken 6 times.
✓ Branch 83 → 91 taken 737 times.
743 if (relation.type.isEmpty() && rawType == QLatin1String("kin") && !apple)
119 6 relation.type = QStringLiteral("kin");
120
6/6
✓ Branch 92 → 93 taken 186 times.
✓ Branch 92 → 96 taken 557 times.
✓ Branch 94 → 95 taken 177 times.
✓ Branch 94 → 96 taken 9 times.
✓ Branch 97 → 98 taken 177 times.
✓ Branch 97 → 106 taken 566 times.
743 if (relation.type.isEmpty() && !label.isEmpty()) {
121 177 relation.type = QStringLiteral("custom");
122 177 relation.label = label;
123 }
124 } else {
125 28989 relation.type = rawType;
126 }
127
1/1
✓ Branch 106 → 107 taken 30934 times.
30934 card->relations.append(relation);
128 35442 }
129
130 304 void readLegacyRelation(const ICal::Property &property, const QString &type, int index, ContactCard *card) {
131
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 304 times.
304 if (card->relations.size() >= MaxRelations)
132 ✗ return;
133 304 ContactRelation relation;
134 304 relation.type = type;
135
1/1
✓ Branch 6 → 7 taken 304 times.
304 relation.targetName = text(property);
136 304 relation.model = QStringLiteral("legacy");
137 304 relation.sourceIndex = index;
138
1/1
✓ Branch 15 → 16 taken 304 times.
304 card->relations.append(relation);
139 304 }
140
141 1712 void readDate(const ICal::Component &tree, int index, ContactCard *card) {
142 1712 const ICal::Property &property = tree.properties.at(index);
143
1/1
✓ Branch 3 → 4 taken 1712 times.
1712 const QString label = groupLabel(tree, property);
144
3/11
✓ Branch 7 → 8 taken 1712 times.
✓ Branch 17 → 18 taken 1712 times.
✗ Branch 17 → 19 not taken.
✓ Branch 19 → 20 taken 1712 times.
✗ Branch 19 → 21 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
5136 const bool met = property.parameter(QStringLiteral("X-MAILJD-ROLE")).compare(QLatin1String("met"),
145
2/2
✓ Branch 10 → 11 taken 941 times.
✓ Branch 10 → 13 taken 771 times.
6077 Qt::CaseInsensitive) == 0 ||
146
4/5
✓ Branch 11 → 12 taken 941 times.
✓ Branch 12 → 13 taken 110 times.
✓ Branch 12 → 14 taken 831 times.
✓ Branch 15 → 16 taken 1712 times.
✗ Branch 15 → 17 not taken.
2653 RelationVocabulary::isMetDateLabel(label);
147
1/1
✓ Branch 21 → 22 taken 1712 times.
1712 const PartialDate date = labelledDate(property);
148
5/6
✓ Branch 22 → 23 taken 881 times.
✓ Branch 22 → 26 taken 831 times.
✓ Branch 24 → 25 taken 881 times.
✗ Branch 24 → 26 not taken.
✓ Branch 27 → 28 taken 881 times.
✓ Branch 27 → 29 taken 831 times.
1712 if (met && !card->met.date.isValid()) {
149 881 card->met.date = {date.year, date.month, date.day};
150 881 return;
151 }
152 831 card->otherDates.append({label, date, index});
153 2543 }
154
155 2438 void readSocial(const ICal::Property &property, const QString &name, int index, ContactCard *card) {
156
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2438 times.
2438 if (card->social.size() >= MaxSocial)
157 ✗ return;
158
1/1
✓ Branch 5 → 6 taken 2438 times.
2438 ContactSocial social;
159
1/1
✓ Branch 6 → 7 taken 2438 times.
2438 social.value = text(property);
160 2438 social.property = name;
161 2438 social.sourceIndex = index;
162
2/2
✓ Branch 12 → 13 taken 1129 times.
✓ Branch 12 → 40 taken 1309 times.
2438 if (name == QLatin1String("IMPP")) {
163 1129 social.kind = QStringLiteral("chat");
164
2/2
✓ Branch 22 → 23 taken 1129 times.
✓ Branch 23 → 24 taken 1129 times.
1129 social.service = property.parameter(QStringLiteral("X-SERVICE-TYPE")).toLower();
165
2/2
✓ Branch 30 → 31 taken 375 times.
✓ Branch 30 → 87 taken 754 times.
1129 if (social.service.isEmpty())
166
2/2
✓ Branch 34 → 35 taken 375 times.
✓ Branch 35 → 36 taken 375 times.
375 social.service = social.value.section(QLatin1Char(':'), 0, 0).toLower();
167
2/2
✓ Branch 42 → 43 taken 1154 times.
✓ Branch 42 → 68 taken 155 times.
1309 } else if (name == QLatin1String("X-SOCIALPROFILE")) {
168
1/1
✓ Branch 43 → 44 taken 1154 times.
1154 social.service = firstType(property);
169
2/2
✓ Branch 47 → 48 taken 4 times.
✓ Branch 47 → 59 taken 1150 times.
1154 if (social.service.isEmpty())
170
2/2
✓ Branch 51 → 52 taken 4 times.
✓ Branch 52 → 53 taken 4 times.
4 social.service = property.parameter(QStringLiteral("X-SERVICE-TYPE")).toLower();
171
1/1
✓ Branch 62 → 63 taken 1154 times.
1154 social.user = property.parameter(QStringLiteral("X-USER"));
172 } else {
173
2/2
✓ Branch 71 → 72 taken 155 times.
✓ Branch 72 → 73 taken 155 times.
155 social.service = property.parameter(QStringLiteral("SERVICE-TYPE")).toLower();
174
1/1
✓ Branch 81 → 82 taken 155 times.
155 social.user = property.parameter(QStringLiteral("USERNAME"));
175 }
176
1/1
✓ Branch 87 → 88 taken 2438 times.
2438 card->social.append(social);
177 2438 }
178
179 1659 int preferenceOf(const ICal::Property &property) {
180 1659 bool ok = false;
181
2/2
✓ Branch 5 → 6 taken 1659 times.
✓ Branch 6 → 7 taken 1659 times.
1659 const int pref = property.parameter(QStringLiteral("PREF")).toInt(&ok);
182
2/2
✓ Branch 10 → 11 taken 1065 times.
✓ Branch 10 → 12 taken 594 times.
3318 return ok ? pref : 100;
183 }
184
185 4936 QString messageIdOf(const ICal::Property &property) {
186
1/1
✓ Branch 2 → 3 taken 4936 times.
4936 const QString value = property.value.trimmed();
187
6/7
✓ Branch 4 → 5 taken 4936 times.
✓ Branch 5 → 6 taken 4881 times.
✓ Branch 5 → 8 taken 55 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 4881 times.
✓ Branch 10 → 11 taken 55 times.
✓ Branch 10 → 12 taken 4881 times.
4936 if (!value.startsWith(QLatin1String("mid:"), Qt::CaseInsensitive) || value.size() > MaxMessageIdLength * 3 + 4)
188 55 return {};
189
3/3
✓ Branch 12 → 13 taken 4881 times.
✓ Branch 13 → 14 taken 4881 times.
✓ Branch 14 → 15 taken 4881 times.
4881 const QString id = QUrl::fromPercentEncoding(value.mid(4).toUtf8());
190
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 4881 times.
4881 return id.size() > MaxMessageIdLength ? QString() : id;
191 4936 }
192
193 const char *const kLegacyNames[][2] = {
194 {"X-EVOLUTION-SPOUSE", "spouse"}, {"X-EVOLUTION-MANAGER", "x-manager"},
195 {"X-EVOLUTION-ASSISTANT", "agent"}, {"X-KADDRESSBOOK-X-SPOUSESNAME", "spouse"},
196 {"X-KADDRESSBOOK-X-MANAGERSNAME", "x-manager"}, {"X-KADDRESSBOOK-X-ASSISTANTSNAME", "agent"},
197 };
198
199 403723 QString legacyType(const QString &name) {
200
2/2
✓ Branch 10 → 3 taken 2421594 times.
✓ Branch 10 → 11 taken 403419 times.
2825013 for (const auto &entry : kLegacyNames)
201
2/2
✓ Branch 5 → 6 taken 304 times.
✓ Branch 5 → 9 taken 2421290 times.
2421594 if (name == QLatin1String(entry[0]))
202
1/1
✓ Branch 7 → 8 taken 304 times.
304 return QString::fromLatin1(entry[1]);
203 403419 return {};
204 }
205
206 394899 bool readMet(const ICal::Property &property, const QString &name, ContactCard *card) {
207
2/2
✓ Branch 4 → 5 taken 415 times.
✓ Branch 4 → 11 taken 394484 times.
394899 if (name == QLatin1String("X-MAILJD-MET-DATE")) {
208
1/2
✓ Branch 6 → 7 taken 415 times.
✗ Branch 6 → 50 not taken.
415 if (!card->met.date.isValid())
209
2/2
✓ Branch 7 → 8 taken 415 times.
✓ Branch 8 → 9 taken 415 times.
415 card->met.date = LooseDate::fromIso(text(property));
210
2/2
✓ Branch 13 → 14 taken 1188 times.
✓ Branch 13 → 26 taken 393296 times.
394484 } else if (name == QLatin1String("X-MAILJD-MET-PLACE")) {
211
1/1
✓ Branch 14 → 15 taken 1188 times.
1188 card->met.place = text(property);
212
2/2
✓ Branch 20 → 21 taken 1188 times.
✓ Branch 21 → 22 taken 1188 times.
1188 card->met.placeGeo = GeoPoint::fromText(property.parameter(QStringLiteral("GEO")));
213
2/2
✓ Branch 28 → 29 taken 293 times.
✓ Branch 28 → 33 taken 393003 times.
393296 } else if (name == QLatin1String("X-MAILJD-MET-EVENT")) {
214
1/1
✓ Branch 29 → 30 taken 293 times.
293 card->met.eventUid = text(property);
215
2/2
✓ Branch 35 → 36 taken 466 times.
✓ Branch 35 → 40 taken 392537 times.
393003 } else if (name == QLatin1String("X-MAILJD-MET-NOTE")) {
216
1/1
✓ Branch 36 → 37 taken 466 times.
466 card->met.note = text(property);
217
2/2
✓ Branch 42 → 43 taken 63 times.
✓ Branch 42 → 49 taken 392474 times.
392537 } else if (name == QLatin1String("X-MAILJD-MET-CHANNEL")) {
218
2/2
✓ Branch 43 → 44 taken 63 times.
✓ Branch 44 → 45 taken 63 times.
63 card->met.channel = text(property).toLower();
219 } else {
220 392474 return false;
221 }
222 2425 return true;
223 }
224
225 392474 void readPersonal(const ICal::Property &property, const QString &name, int index, ContactCard *card,
226 QList<QPair<QPair<int, int>, QString>> *languages) {
227
6/6
✓ Branch 4 → 5 taken 392010 times.
✓ Branch 4 → 8 taken 464 times.
✓ Branch 7 → 8 taken 1195 times.
✓ Branch 7 → 9 taken 390815 times.
✓ Branch 10 → 11 taken 1659 times.
✓ Branch 10 → 19 taken 390815 times.
392474 if (name == QLatin1String("LANG") || name == QLatin1String("X-MAILJD-LANG")) {
228
3/3
✓ Branch 11 → 12 taken 1659 times.
✓ Branch 13 → 14 taken 1659 times.
✓ Branch 15 → 16 taken 1659 times.
1659 languages->append({{preferenceOf(property), index}, text(property)});
229
4/4
✓ Branch 24 → 25 taken 389213 times.
✓ Branch 24 → 31 taken 1188 times.
✓ Branch 27 → 28 taken 389166 times.
✓ Branch 27 → 31 taken 47 times.
1170429 } else if (name == QLatin1String("INTEREST") || name == QLatin1String("X-MAILJD-INTEREST") ||
230
6/6
✓ Branch 21 → 22 taken 390401 times.
✓ Branch 21 → 31 taken 414 times.
✓ Branch 30 → 31 taken 47 times.
✓ Branch 30 → 32 taken 389119 times.
✓ Branch 33 → 34 taken 1696 times.
✓ Branch 33 → 40 taken 389119 times.
1170429 name == QLatin1String("HOBBY") || name == QLatin1String("EXPERTISE")) {
231
2/3
✓ Branch 34 → 35 taken 1696 times.
✓ Branch 36 → 37 taken 1696 times.
✗ Branch 36 → 38 not taken.
1696 if (const QString value = text(property); !value.isEmpty())
232
1/1
✓ Branch 37 → 38 taken 1696 times.
1696 card->interests.append(value);
233
7/8
✓ Branch 42 → 43 taken 389046 times.
✓ Branch 42 → 46 taken 73 times.
✓ Branch 45 → 46 taken 577 times.
✓ Branch 45 → 49 taken 388469 times.
✓ Branch 47 → 48 taken 650 times.
✗ Branch 47 → 49 not taken.
✓ Branch 50 → 51 taken 650 times.
✓ Branch 50 → 55 taken 388469 times.
389769 } else if ((name == QLatin1String("BIRTHPLACE") || name == QLatin1String("X-MAILJD-BIRTHPLACE")) &&
234 650 card->birthplace.isEmpty()) {
235
1/1
✓ Branch 51 → 52 taken 650 times.
650 card->birthplace = text(property);
236
5/6
✓ Branch 57 → 58 taken 155 times.
✓ Branch 57 → 60 taken 388314 times.
✓ Branch 58 → 59 taken 155 times.
✗ Branch 58 → 60 not taken.
✓ Branch 61 → 62 taken 155 times.
✓ Branch 61 → 64 taken 388314 times.
388469 } else if (name == QLatin1String("GEO") && !card->geo.valid) {
237
1/1
✓ Branch 62 → 63 taken 155 times.
155 card->geo = GeoPoint::fromText(property.value);
238
5/6
✓ Branch 66 → 67 taken 234 times.
✓ Branch 66 → 70 taken 388080 times.
✓ Branch 68 → 69 taken 234 times.
✗ Branch 68 → 70 not taken.
✓ Branch 71 → 72 taken 234 times.
✓ Branch 71 → 76 taken 388080 times.
388314 } else if (name == QLatin1String("TZ") && card->timeZone.isEmpty()) {
239
1/1
✓ Branch 72 → 73 taken 234 times.
234 card->timeZone = text(property);
240
8/8
✓ Branch 78 → 79 taken 387310 times.
✓ Branch 78 → 82 taken 770 times.
✓ Branch 81 → 82 taken 1683 times.
✓ Branch 81 → 85 taken 385627 times.
✓ Branch 83 → 84 taken 1668 times.
✓ Branch 83 → 85 taken 785 times.
✓ Branch 86 → 87 taken 1668 times.
✓ Branch 86 → 108 taken 386412 times.
388080 } else if ((name == QLatin1String("GENDER") || name == QLatin1String("X-GENDER")) && card->gender.isEmpty()) {
241 // F103-41: editable under *Add detail*, no longer listed under "More".
242
3/3
✓ Branch 87 → 88 taken 1668 times.
✓ Branch 91 → 92 taken 1668 times.
✓ Branch 92 → 93 taken 1668 times.
3336 card->gender = text(property).section(QLatin1Char(';'), 0, 0).trimmed();
243
3/3
✓ Branch 97 → 98 taken 1668 times.
✓ Branch 101 → 102 taken 1668 times.
✓ Branch 102 → 103 taken 1668 times.
3336 card->genderIdentity = text(property).section(QLatin1Char(';'), 1).trimmed();
244
6/6
✓ Branch 110 → 111 taken 386365 times.
✓ Branch 110 → 114 taken 47 times.
✓ Branch 113 → 114 taken 57 times.
✓ Branch 113 → 115 taken 386308 times.
✓ Branch 116 → 117 taken 104 times.
✓ Branch 116 → 122 taken 386308 times.
386412 } else if (name == QLatin1String("PRONOUNS") || name == QLatin1String("X-PRONOUNS")) {
245 // D-9: pronouns are kept and shown read-only, never edited.
246 104 card->extraFields.append({name, QString(), text(property)});
247
2/2
✓ Branch 124 → 125 taken 4936 times.
✓ Branch 124 → 141 taken 381372 times.
386308 } else if (name == QLatin1String("X-MAILJD-MAIL")) {
248 14808 card->mailLinks.append({messageIdOf(property), property.parameter(QStringLiteral("X-SUBJECT")),
249 9872 property.parameter(QStringLiteral("X-DATE")), index});
250 }
251 407386 }
252
253 } // namespace
254
255 72182 void readPeople(const ICal::Component &tree, ContactCard *card) {
256 72182 QList<QPair<QPair<int, int>, QString>> languages;
257 72182 int addressIndex = 0;
258
2/2
✓ Branch 60 → 3 taken 436911 times.
✓ Branch 60 → 61 taken 72182 times.
509093 for (int i = 0; i < tree.properties.size(); ++i) {
259 436911 const ICal::Property &property = tree.properties.at(i);
260
1/1
✓ Branch 4 → 5 taken 436911 times.
436911 const QString name = baseName(property);
261
6/6
✓ Branch 7 → 8 taken 404254 times.
✓ Branch 7 → 11 taken 32657 times.
✓ Branch 10 → 11 taken 531 times.
✓ Branch 10 → 12 taken 403723 times.
✓ Branch 13 → 14 taken 33188 times.
✓ Branch 13 → 15 taken 403723 times.
436911 if (name == QLatin1String("RELATED") || name == QLatin1String("X-ABRELATEDNAMES")) {
262
1/1
✓ Branch 14 → 57 taken 33188 times.
33188 readRelation(tree, i, card);
263
3/3
✓ Branch 15 → 16 taken 403723 times.
✓ Branch 17 → 18 taken 304 times.
✓ Branch 17 → 19 taken 403419 times.
403723 } else if (const QString legacy = legacyType(name); !legacy.isEmpty()) {
264
1/1
✓ Branch 18 → 55 taken 304 times.
304 readLegacyRelation(property, legacy, i, card);
265
2/2
✓ Branch 21 → 22 taken 1712 times.
✓ Branch 21 → 23 taken 401707 times.
403419 } else if (name == QLatin1String("X-ABDATE")) {
266
1/1
✓ Branch 22 → 55 taken 1712 times.
1712 readDate(tree, i, card);
267
6/6
✓ Branch 25 → 26 taken 400553 times.
✓ Branch 25 → 32 taken 1154 times.
✓ Branch 28 → 29 taken 400398 times.
✓ Branch 28 → 32 taken 155 times.
✓ Branch 31 → 32 taken 1129 times.
✓ Branch 31 → 33 taken 399269 times.
802105 } else if (name == QLatin1String("X-SOCIALPROFILE") || name == QLatin1String("SOCIALPROFILE") ||
268
2/2
✓ Branch 34 → 35 taken 2438 times.
✓ Branch 34 → 36 taken 399269 times.
802105 name == QLatin1String("IMPP")) {
269
1/1
✓ Branch 35 → 55 taken 2438 times.
2438 readSocial(property, name, i, card);
270
2/2
✓ Branch 38 → 39 taken 4370 times.
✓ Branch 38 → 52 taken 394899 times.
399269 } else if (name == QLatin1String("ADR")) {
271 // Addresses were read by readMultiples in the same order.
272
1/2
✓ Branch 40 → 41 taken 4370 times.
✗ Branch 40 → 51 not taken.
4370 if (addressIndex < card->addresses.size())
273
3/3
✓ Branch 44 → 45 taken 4370 times.
✓ Branch 45 → 46 taken 4370 times.
✓ Branch 46 → 47 taken 4370 times.
4370 card->addresses[addressIndex].geo = GeoPoint::fromText(property.parameter(QStringLiteral("GEO")));
274 4370 ++addressIndex;
275
3/3
✓ Branch 52 → 53 taken 394899 times.
✓ Branch 53 → 54 taken 392474 times.
✓ Branch 53 → 55 taken 2425 times.
394899 } else if (!readMet(property, name, card)) {
276
1/1
✓ Branch 54 → 55 taken 392474 times.
392474 readPersonal(property, name, i, card, &languages);
277 403723 }
278 436911 }
279
3/3
✓ Branch 61 → 62 taken 72182 times.
✓ Branch 62 → 63 taken 72182 times.
✓ Branch 63 → 64 taken 72182 times.
72182 std::stable_sort(languages.begin(), languages.end(),
280 937 [](const auto &a, const auto &b) { return a.first.first < b.first.first; });
281
4/4
✓ Branch 64 → 65 taken 72182 times.
✓ Branch 65 → 66 taken 72182 times.
✓ Branch 73 → 67 taken 1659 times.
✓ Branch 73 → 74 taken 72182 times.
73841 for (const auto &language : languages)
282
1/2
✓ Branch 69 → 70 taken 1659 times.
✗ Branch 69 → 71 not taken.
1659 if (!language.second.isEmpty())
283
1/1
✓ Branch 70 → 71 taken 1659 times.
1659 card->languages.append(language.second);
284 72182 }
285
286 } // namespace VCardCodec
287
288 // ─── value types ────────────────────────────────────────────
289
290 1943 GeoPoint GeoPoint::at(double latitude, double longitude) {
291 1943 GeoPoint point;
292
9/14
✓ Branch 2 → 3 taken 1943 times.
✗ Branch 2 → 10 not taken.
✓ Branch 3 → 4 taken 1942 times.
✓ Branch 3 → 10 taken 1 time.
✓ Branch 4 → 5 taken 1942 times.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 1942 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 1942 times.
✗ Branch 7 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1942 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 1942 times.
3885 if (latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 || std::isnan(latitude) ||
293 1942 std::isnan(longitude))
294 1 return point;
295 1942 point.latitude = latitude;
296 1942 point.longitude = longitude;
297 1942 point.valid = true;
298 1942 return point;
299 }
300
301 8279 GeoPoint GeoPoint::fromText(const QString &input) {
302
1/1
✓ Branch 2 → 3 taken 8279 times.
8279 QString value = input.trimmed();
303 8279 QStringList parts;
304
3/3
✓ Branch 4 → 5 taken 8279 times.
✓ Branch 5 → 6 taken 587 times.
✓ Branch 5 → 21 taken 7692 times.
8279 if (value.startsWith(QLatin1String("geo:"), Qt::CaseInsensitive)) {
305
2/2
✓ Branch 6 → 7 taken 587 times.
✓ Branch 10 → 11 taken 587 times.
1174 value = value.mid(4).section(QLatin1Char(';'), 0, 0);
306
1/1
✓ Branch 17 → 18 taken 587 times.
587 parts = value.split(QLatin1Char(','));
307 } else {
308
1/1
✓ Branch 25 → 26 taken 7692 times.
7692 value.replace(QLatin1Char(','), QLatin1Char(';'));
309
1/1
✓ Branch 29 → 30 taken 7692 times.
7692 parts = value.split(QLatin1Char(';'));
310 }
311
2/2
✓ Branch 34 → 35 taken 7628 times.
✓ Branch 34 → 36 taken 651 times.
8279 if (parts.size() < 2)
312 7628 return {};
313 651 bool latOk = false;
314 651 bool lonOk = false;
315
2/2
✓ Branch 37 → 38 taken 651 times.
✓ Branch 38 → 39 taken 651 times.
651 const double latitude = parts.at(0).trimmed().toDouble(&latOk);
316
2/2
✓ Branch 41 → 42 taken 651 times.
✓ Branch 42 → 43 taken 651 times.
651 const double longitude = parts.at(1).trimmed().toDouble(&lonOk);
317
4/4
✓ Branch 44 → 45 taken 613 times.
✓ Branch 44 → 47 taken 38 times.
✓ Branch 45 → 46 taken 610 times.
✓ Branch 45 → 47 taken 3 times.
651 return latOk && lonOk ? at(latitude, longitude) : GeoPoint{};
318 8279 }
319
320 100 QString GeoPoint::toUri() const {
321
2/2
✓ Branch 2 → 3 taken 90 times.
✓ Branch 2 → 4 taken 10 times.
100 if (!valid)
322 90 return {};
323
3/3
✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 10 times.
✓ Branch 9 → 10 taken 10 times.
10 return QStringLiteral("geo:%1,%2").arg(QString::number(latitude, 'g', 9), QString::number(longitude, 'g', 9));
324 }
325
326 4265 bool LooseDate::isValid() const {
327
4/4
✓ Branch 2 → 3 taken 2886 times.
✓ Branch 2 → 5 taken 1379 times.
✓ Branch 3 → 4 taken 2881 times.
✓ Branch 3 → 5 taken 5 times.
4265 if (year <= 0 && month <= 0)
328 2881 return false;
329
5/8
✓ Branch 5 → 6 taken 1384 times.
✗ Branch 5 → 9 not taken.
✓ Branch 6 → 7 taken 1379 times.
✓ Branch 6 → 9 taken 5 times.
✓ Branch 7 → 8 taken 1379 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1379 times.
1384 if (month < 0 || month > 12 || day < 0 || day > 31)
330 5 return false;
331
3/4
✓ Branch 10 → 11 taken 247 times.
✓ Branch 10 → 13 taken 1132 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 247 times.
1379 if (day > 0 && month == 0)
332 ✗ return false;
333
3/4
✓ Branch 13 → 14 taken 4 times.
✓ Branch 13 → 16 taken 1375 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 4 times.
1379 if (year <= 0 && day == 0)
334 ✗ return false; // a month alone is no date
335
2/4
✓ Branch 16 → 17 taken 1379 times.
✗ Branch 16 → 19 not taken.
✓ Branch 17 → 18 taken 1379 times.
✗ Branch 17 → 19 not taken.
1379 return year >= 0 && year <= 9999;
336 }
337
338 245 QString LooseDate::toIso() const {
339
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 243 times.
245 if (!isValid())
340 2 return {};
341
1/1
✓ Branch 7 → 8 taken 221 times.
442 const auto two = [](int value) { return QStringLiteral("%1").arg(value, 2, 10, QLatin1Char('0')); };
342
2/2
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 24 taken 241 times.
243 if (year <= 0)
343
5/5
✓ Branch 6 → 7 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 15 → 16 taken 2 times.
4 return QStringLiteral("--") + two(month) + QLatin1Char('-') + two(day);
344
1/1
✓ Branch 29 → 30 taken 241 times.
241 QString iso = QStringLiteral("%1").arg(year, 4, 10, QLatin1Char('0'));
345
2/2
✓ Branch 32 → 33 taken 178 times.
✓ Branch 32 → 41 taken 63 times.
241 if (month > 0)
346
3/3
✓ Branch 33 → 34 taken 178 times.
✓ Branch 36 → 37 taken 178 times.
✓ Branch 37 → 38 taken 178 times.
178 iso += QLatin1Char('-') + two(month);
347
2/2
✓ Branch 41 → 42 taken 39 times.
✓ Branch 41 → 50 taken 202 times.
241 if (day > 0)
348
3/3
✓ Branch 42 → 43 taken 39 times.
✓ Branch 45 → 46 taken 39 times.
✓ Branch 46 → 47 taken 39 times.
39 iso += QLatin1Char('-') + two(day);
349 241 return iso;
350 241 }
351
352 619 LooseDate LooseDate::fromIso(const QString &input) {
353
1/1
✓ Branch 2 → 3 taken 619 times.
619 const QString value = input.trimmed();
354 619 LooseDate date;
355
3/3
✓ Branch 4 → 5 taken 619 times.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 25 taken 617 times.
619 if (value.startsWith(QLatin1String("--"))) {
356
1/1
✓ Branch 6 → 7 taken 2 times.
2 QString digits = value.mid(2);
357
1/1
✓ Branch 9 → 10 taken 2 times.
2 digits.remove(QLatin1Char('-'));
358
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 2 times.
2 if (digits.size() != 4)
359 ✗ return {};
360
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
2 date.month = digits.left(2).toInt();
361
2/2
✓ Branch 16 → 17 taken 2 times.
✓ Branch 17 → 18 taken 2 times.
2 date.day = digits.mid(2).toInt();
362
1/2
✓ Branch 21 → 22 taken 2 times.
✗ Branch 21 → 24 not taken.
2 } else {
363
1/1
✓ Branch 28 → 29 taken 617 times.
617 QString digits = value.section(QLatin1Char('T'), 0, 0);
364
1/1
✓ Branch 31 → 32 taken 617 times.
617 digits.remove(QLatin1Char('-'));
365
4/4
✓ Branch 32 → 33 taken 617 times.
✓ Branch 33 → 34 taken 617 times.
✓ Branch 39 → 35 taken 3346 times.
✓ Branch 39 → 40 taken 617 times.
3963 for (const QChar c : digits)
366
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 3346 times.
3346 if (!c.isDigit())
367 ✗ return {};
368
8/8
✓ Branch 41 → 42 taken 423 times.
✓ Branch 41 → 47 taken 194 times.
✓ Branch 43 → 44 taken 68 times.
✓ Branch 43 → 47 taken 355 times.
✓ Branch 45 → 46 taken 13 times.
✓ Branch 45 → 47 taken 55 times.
✓ Branch 48 → 49 taken 13 times.
✓ Branch 48 → 50 taken 604 times.
617 if (digits.size() != 4 && digits.size() != 6 && digits.size() != 8)
369 13 return {};
370
2/2
✓ Branch 50 → 51 taken 604 times.
✓ Branch 51 → 52 taken 604 times.
604 date.year = digits.left(4).toInt();
371
2/2
✓ Branch 54 → 55 taken 410 times.
✓ Branch 54 → 59 taken 194 times.
604 if (digits.size() >= 6)
372
2/2
✓ Branch 55 → 56 taken 410 times.
✓ Branch 56 → 57 taken 410 times.
410 date.month = digits.mid(4, 2).toInt();
373
2/2
✓ Branch 60 → 61 taken 55 times.
✓ Branch 60 → 65 taken 549 times.
604 if (digits.size() == 8)
374
2/2
✓ Branch 61 → 62 taken 55 times.
✓ Branch 62 → 63 taken 55 times.
55 date.day = digits.mid(6, 2).toInt();
375 617 }
376
2/2
✓ Branch 72 → 73 taken 602 times.
✓ Branch 72 → 74 taken 4 times.
606 return date.isValid() ? date : LooseDate{};
377 619 }
378