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: 94.4% 422 / 0 / 447
Functions: 97.1% 33 / 0 / 34
Branches: 76.6% 635 / 0 / 829

data/VCardPatch.cpp
Line Branch Exec Source
1 // Write side of VCardCodec (S97.1.1): patch() and create().
2 #include "VCardCodec.h"
3 #include "VCardWriter.h"
4
5 #include <QLoggingCategory>
6 #include <QSet>
7 #include <QTimeZone>
8
9 Q_DECLARE_LOGGING_CATEGORY(lcVCard)
10
11 namespace VCardCodec {
12
13 namespace detail {
14
15 const QSet<QString> kStructuredNames{QStringLiteral("N"), QStringLiteral("ADR"), QStringLiteral("ORG")};
16
17 28781 QString escapeComponents(const QStringList &parts) {
18 28781 QStringList escaped;
19
2/2
✓ Branch 10 → 4 taken 146156 times.
✓ Branch 10 → 11 taken 28781 times.
174937 for (const QString &part : parts)
20
2/2
✓ Branch 5 → 6 taken 146156 times.
✓ Branch 6 → 7 taken 146156 times.
146156 escaped.append(ICal::escapeText(part));
21
1/1
✓ Branch 13 → 14 taken 28781 times.
57562 return escaped.join(QLatin1Char(';'));
22 28781 }
23
24 // ─── vCard 2.1 → 3.0 (first write of an old card) ───
25
26 // vCard 2.1 values are not TEXT-escaped, but writers escape the separator
27 // (\;) and, in practice, \, and \\ as well. S106-151: the escapes are
28 // read in order — "\\;" is a backslash and then a separator — and undone
29 // before the value is escaped for 3.0 (a stray backslash used to remain).
30 180 QStringList splitRaw21(const QString &value, bool structured) {
31 180 QStringList parts;
32 180 QString current;
33
2/2
✓ Branch 42 → 3 taken 3600 times.
✓ Branch 42 → 43 taken 180 times.
3780 for (qsizetype i = 0; i < value.size(); ++i) {
34 3600 const QChar c = value.at(i);
35
2/2
✓ Branch 5 → 6 taken 3423 times.
✓ Branch 5 → 7 taken 177 times.
3600 const QChar next = i + 1 < value.size() ? value.at(i + 1) : QChar();
36
4/4
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 25 taken 3597 times.
✓ Branch 15 → 16 taken 2 times.
✓ Branch 15 → 24 taken 1 time.
3605 if (c == QLatin1Char('\\') &&
37
5/6
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 24 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 29 taken 3597 times.
3605 (next == QLatin1Char('\\') || next == QLatin1Char(';') || next == QLatin1Char(','))) {
38
1/1
✓ Branch 27 → 28 taken 3 times.
3 current += next;
39 3 ++i;
40
6/6
✓ Branch 29 → 30 taken 997 times.
✓ Branch 29 → 35 taken 2600 times.
✓ Branch 33 → 34 taken 94 times.
✓ Branch 33 → 35 taken 903 times.
✓ Branch 36 → 37 taken 94 times.
✓ Branch 36 → 39 taken 3503 times.
3597 } else if (structured && c == QLatin1Char(';')) {
41
1/1
✓ Branch 37 → 38 taken 94 times.
94 parts.append(current);
42 94 current.clear();
43 } else {
44
1/1
✓ Branch 39 → 40 taken 3503 times.
3503 current += c;
45 }
46 }
47
1/1
✓ Branch 43 → 44 taken 180 times.
180 parts.append(current);
48 180 return parts;
49 180 }
50
51 180 void upgradeProperty(ICal::Property *property) {
52
1/1
✓ Branch 2 → 3 taken 180 times.
180 const QString name = baseName(*property);
53
2/2
✓ Branch 6 → 7 taken 180 times.
✓ Branch 7 → 8 taken 180 times.
360 const QString encoding = property->parameter(QStringLiteral("ENCODING")).toUpper();
54
8/13
✓ Branch 13 → 14 taken 146 times.
✓ Branch 13 → 19 taken 34 times.
✓ Branch 17 → 18 taken 146 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 146 times.
✓ Branch 21 → 22 taken 146 times.
✓ Branch 21 → 23 taken 34 times.
✓ Branch 23 → 24 taken 146 times.
✓ Branch 23 → 25 taken 34 times.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
✗ Branch 139 → 140 not taken.
✗ Branch 139 → 141 not taken.
326 const bool qp = encoding == QLatin1String("QUOTED-PRINTABLE") || property->hasParameter(QStringLiteral("QUOTED-PRINTABLE"));
55
5/13
✓ Branch 27 → 28 taken 180 times.
✗ Branch 27 → 33 not taken.
✓ Branch 31 → 32 taken 180 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 180 times.
✓ Branch 35 → 36 taken 180 times.
✗ Branch 35 → 37 not taken.
✓ Branch 37 → 38 taken 180 times.
✗ Branch 37 → 39 not taken.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 150 not taken.
360 const bool base64 = encoding == QLatin1String("BASE64") || property->hasParameter(QStringLiteral("BASE64"));
56 // 2.1 values are not TEXT-escaped; decode quoted-printable first.
57
3/3
✓ Branch 39 → 40 taken 34 times.
✓ Branch 39 → 41 taken 146 times.
✓ Branch 40 → 42 taken 34 times.
180 const QString text = qp ? decodedValue(*property) : property->value;
58 180 QList<ICal::Parameter> parameters;
59 180 QStringList types;
60
4/4
✓ Branch 42 → 43 taken 180 times.
✓ Branch 43 → 44 taken 180 times.
✓ Branch 72 → 45 taken 196 times.
✓ Branch 72 → 73 taken 180 times.
376 for (const ICal::Parameter &parameter : property->parameters) {
61
3/4
✓ Branch 51 → 52 taken 121 times.
✓ Branch 51 → 58 taken 35 times.
✓ Branch 54 → 55 taken 121 times.
✗ Branch 54 → 58 not taken.
473 if (parameter.name == QLatin1String("ENCODING") || parameter.name == QLatin1String("CHARSET") ||
62
5/6
✓ Branch 48 → 49 taken 156 times.
✓ Branch 48 → 58 taken 40 times.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 121 times.
✓ Branch 60 → 61 taken 75 times.
✓ Branch 60 → 62 taken 121 times.
473 parameter.name == QLatin1String("QUOTED-PRINTABLE") || parameter.name == QLatin1String("8BIT"))
63 75 continue;
64
1/2
✗ Branch 64 → 65 not taken.
✓ Branch 64 → 66 taken 121 times.
121 if (parameter.name == QLatin1String("BASE64"))
65 ✗ continue;
66
2/2
✓ Branch 67 → 68 taken 113 times.
✓ Branch 67 → 69 taken 8 times.
121 if (parameter.value.isNull())
67
1/1
✓ Branch 68 → 70 taken 113 times.
113 types.append(parameter.name);
68 else
69
1/1
✓ Branch 69 → 70 taken 8 times.
8 parameters.append(parameter);
70 }
71
1/2
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 85 taken 180 times.
180 if (base64)
72 ✗ parameters.prepend({QStringLiteral("ENCODING"), QStringLiteral("b")});
73
4/4
✓ Branch 85 → 86 taken 180 times.
✓ Branch 86 → 87 taken 180 times.
✓ Branch 98 → 88 taken 113 times.
✓ Branch 98 → 99 taken 180 times.
293 for (const QString &type : types)
74 113 parameters.append({QStringLiteral("TYPE"), type});
75 180 property->parameters = parameters;
76
1/2
✓ Branch 100 → 101 taken 180 times.
✗ Branch 100 → 119 not taken.
180 if (!base64) {
77
2/2
✓ Branch 102 → 103 taken 56 times.
✓ Branch 102 → 109 taken 124 times.
180 if (kStructuredNames.contains(name))
78
2/2
✓ Branch 103 → 104 taken 56 times.
✓ Branch 104 → 105 taken 56 times.
56 property->value = escapeComponents(splitRaw21(text, true));
79
1/2
✓ Branch 111 → 112 taken 124 times.
✗ Branch 111 → 119 not taken.
124 else if (name != QLatin1String("VERSION"))
80
3/3
✓ Branch 112 → 113 taken 124 times.
✓ Branch 113 → 114 taken 124 times.
✓ Branch 114 → 115 taken 124 times.
124 property->value = ICal::escapeText(splitRaw21(text, false).first());
81 }
82 180 property->originalLine.clear();
83 293 }
84
85 // `origin` (the patch writer's line mapping) gains -1 for a line added
86 // here, so its indices keep matching the properties.
87 38 void upgradeTo30(ICal::Component *card, QList<int> *origin) {
88
4/4
✓ Branch 2 → 3 taken 38 times.
✓ Branch 3 → 4 taken 38 times.
✓ Branch 21 → 5 taken 218 times.
✓ Branch 21 → 22 taken 38 times.
256 for (ICal::Property &property : card->properties) {
89
3/3
✓ Branch 7 → 8 taken 218 times.
✓ Branch 10 → 11 taken 38 times.
✓ Branch 10 → 18 taken 180 times.
218 if (baseName(property) == QLatin1String("VERSION"))
90
1/1
✓ Branch 14 → 15 taken 38 times.
38 property.setValue(QStringLiteral("3.0"));
91 else
92
1/1
✓ Branch 18 → 19 taken 180 times.
180 upgradeProperty(&property);
93 }
94 // A 2.1 AGENT holds a nested card; 3.0 has no nested components but
95 // carries the agent's card as the escaped text value of AGENT (RFC 2426
96 // §3.5.4). S106-151: the nested card used to be dropped.
97 38 int agent = 0;
98
4/4
✓ Branch 22 → 23 taken 38 times.
✓ Branch 23 → 24 taken 38 times.
✓ Branch 82 → 25 taken 4 times.
✓ Branch 82 → 83 taken 38 times.
42 for (ICal::Component nested : card->components) {
99
1/2
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 4 times.
4 if (nested.name != QLatin1String("VCARD"))
100 ✗ continue;
101
1/1
✓ Branch 31 → 32 taken 4 times.
4 upgradeTo30(&nested, nullptr);
102
2/2
✓ Branch 32 → 33 taken 4 times.
✓ Branch 33 → 34 taken 4 times.
4 const QString value = ICal::escapeText(ICal::serialize(nested));
103
2/2
✓ Branch 38 → 39 taken 16 times.
✓ Branch 38 → 49 taken 1 time.
33 while (agent < card->properties.size() &&
104
5/7
✓ Branch 41 → 42 taken 16 times.
✓ Branch 43 → 44 taken 3 times.
✓ Branch 43 → 48 taken 13 times.
✓ Branch 54 → 36 taken 13 times.
✓ Branch 54 → 55 taken 4 times.
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 103 not taken.
33 !(baseName(card->properties.at(agent)) == QLatin1String("AGENT") &&
105
6/9
✓ Branch 45 → 46 taken 3 times.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 3 times.
✓ Branch 50 → 51 taken 3 times.
✓ Branch 50 → 52 taken 14 times.
✓ Branch 52 → 53 taken 16 times.
✓ Branch 52 → 54 taken 1 time.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
20 card->properties.at(agent).value.trimmed().isEmpty()))
106 13 ++agent;
107
2/2
✓ Branch 56 → 57 taken 3 times.
✓ Branch 56 → 60 taken 1 time.
4 if (agent < card->properties.size()) {
108
2/2
✓ Branch 57 → 58 taken 3 times.
✓ Branch 58 → 59 taken 3 times.
3 card->properties[agent].setValue(value);
109 3 ++agent;
110 } else {
111 1 ICal::Property property;
112 1 property.name = QStringLiteral("AGENT");
113 1 property.value = value;
114
1/1
✓ Branch 67 → 68 taken 1 time.
1 card->appendProperty(property);
115
1/2
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 70 not taken.
1 if (origin)
116
1/1
✓ Branch 69 → 70 taken 1 time.
1 origin->append(-1);
117 1 agent = int(card->properties.size());
118 1 }
119 4 }
120 38 card->components.clear();
121 38 }
122
123 // ─── single-valued properties ───
124
125 136607 int indexOfName(const ICal::Component &card, const QString &name) {
126
2/2
✓ Branch 11 → 3 taken 840736 times.
✓ Branch 11 → 12 taken 104231 times.
944967 for (int i = 0; i < card.properties.size(); ++i)
127
3/3
✓ Branch 4 → 5 taken 840736 times.
✓ Branch 7 → 8 taken 32376 times.
✓ Branch 7 → 9 taken 808360 times.
840736 if (baseName(card.properties.at(i)) == name)
128 32376 return i;
129 104231 return -1;
130 }
131
132 // Sets (or removes, for an empty value) the first property `name`, keeping
133 // its position, group and other parameters.
134 77547 void setSingle(Writer *w, const QString &name, const QString &wireValue) {
135
1/1
✓ Branch 2 → 3 taken 77547 times.
77547 const int index = indexOfName(w->card, name);
136
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 8 taken 77545 times.
77547 if (wireValue.isEmpty()) {
137
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 7 not taken.
2 if (index >= 0)
138
1/1
✓ Branch 6 → 7 taken 2 times.
2 w->removeAt(index);
139 971 return;
140 }
141
2/2
✓ Branch 8 → 9 taken 969 times.
✓ Branch 8 → 16 taken 76576 times.
77545 if (index >= 0) {
142
2/2
✓ Branch 11 → 12 taken 859 times.
✓ Branch 11 → 15 taken 110 times.
969 if (w->card.properties.at(index).value != wireValue) {
143
2/2
✓ Branch 12 → 13 taken 859 times.
✓ Branch 13 → 14 taken 859 times.
859 w->card.properties[index].setValue(wireValue);
144 859 w->changed = true;
145 }
146 969 return;
147 }
148 76576 ICal::Property created;
149 76576 created.name = name;
150 76576 created.value = wireValue;
151
1/1
✓ Branch 18 → 19 taken 76576 times.
76576 w->append(created);
152 76576 }
153
154 488 QString dateWire(const PartialDate &date, bool v4) {
155
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 488 times.
488 if (!date.isValid())
156 ✗ return {};
157
2/2
✓ Branch 10 → 11 taken 488 times.
✓ Branch 13 → 14 taken 488 times.
488 const QString monthDay = QStringLiteral("%1%2").arg(date.month, 2, 10, QLatin1Char('0')).arg(date.day, 2, 10, QLatin1Char('0'));
158
2/2
✓ Branch 17 → 18 taken 119 times.
✓ Branch 17 → 43 taken 369 times.
488 if (v4)
159
5/11
✓ Branch 25 → 26 taken 71 times.
✓ Branch 38 → 39 taken 71 times.
✓ Branch 38 → 40 taken 48 times.
✓ Branch 40 → 41 taken 71 times.
✓ Branch 40 → 42 taken 48 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 94 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
309 return date.hasYear() ? QStringLiteral("%1").arg(date.year, 4, 10, QLatin1Char('0')) + monthDay
160
10/14
✓ Branch 19 → 20 taken 71 times.
✓ Branch 19 → 27 taken 48 times.
✓ Branch 26 → 31 taken 71 times.
✓ Branch 30 → 31 taken 48 times.
✓ Branch 32 → 33 taken 48 times.
✓ Branch 32 → 34 taken 71 times.
✓ Branch 34 → 35 taken 48 times.
✓ Branch 34 → 36 taken 71 times.
✓ Branch 36 → 37 taken 71 times.
✓ Branch 36 → 38 taken 48 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
357 : QStringLiteral("--") + monthDay;
161
14/22
✓ Branch 44 → 45 taken 307 times.
✓ Branch 44 → 46 taken 62 times.
✓ Branch 45 → 52 taken 307 times.
✓ Branch 46 → 47 taken 62 times.
✓ Branch 47 → 48 taken 62 times.
✓ Branch 51 → 52 taken 62 times.
✓ Branch 53 → 54 taken 62 times.
✓ Branch 53 → 55 taken 307 times.
✓ Branch 55 → 56 taken 62 times.
✓ Branch 55 → 57 taken 307 times.
✓ Branch 57 → 58 taken 62 times.
✓ Branch 57 → 59 taken 307 times.
✓ Branch 59 → 60 taken 62 times.
✓ Branch 59 → 61 taken 307 times.
✗ 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.
✗ Branch 112 → 113 not taken.
✗ Branch 112 → 114 not taken.
431 return date.hasYear() ? date.toIso() : QStringLiteral("1604-") + date.toIso().mid(2);
162 488 }
163
164 352 void setDate(Writer *w, const QString &name, const PartialDate &date) {
165
1/1
✓ Branch 2 → 3 taken 352 times.
352 const int index = indexOfName(w->card, name);
166
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 351 times.
352 if (index >= 0)
167
1/1
✓ Branch 4 → 5 taken 1 time.
1 w->removeAt(index);
168
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 352 times.
352 if (!date.isValid())
169 ✗ return;
170 352 ICal::Property created;
171 352 created.name = name;
172
1/1
✓ Branch 9 → 10 taken 352 times.
352 created.value = dateWire(date, w->v4);
173 // Apple's marker for a date without a year; vCard 4 writes "--MMDD".
174
6/6
✓ Branch 12 → 13 taken 261 times.
✓ Branch 12 → 16 taken 91 times.
✓ Branch 14 → 15 taken 51 times.
✓ Branch 14 → 16 taken 210 times.
✓ Branch 17 → 18 taken 51 times.
✓ Branch 17 → 29 taken 301 times.
352 if (!w->v4 && !date.hasYear())
175 51 created.parameters.append({QStringLiteral("X-APPLE-OMIT-YEAR"), QStringLiteral("1604")});
176
3/3
✓ Branch 29 → 30 taken 351 times.
✓ Branch 29 → 32 taken 1 time.
✓ Branch 33 → 34 taken 352 times.
352 w->insert(index < 0 ? int(w->card.properties.size()) : index, created);
177 454 }
178
179 // ─── multi-valued properties ───
180
181 738 QString freeGroup(const ICal::Component &card) {
182 738 int highest = 0;
183
2/2
✓ Branch 19 → 4 taken 12634 times.
✓ Branch 19 → 20 taken 738 times.
13372 for (const ICal::Property &property : card.properties) {
184
2/2
✓ Branch 5 → 6 taken 12634 times.
✓ Branch 6 → 7 taken 12634 times.
12634 const QString group = groupOf(property).toUpper();
185
3/3
✓ Branch 9 → 10 taken 12634 times.
✓ Branch 10 → 11 taken 2391 times.
✓ Branch 10 → 16 taken 10243 times.
12634 if (group.startsWith(QLatin1String("ITEM")))
186
2/2
✓ Branch 11 → 12 taken 2391 times.
✓ Branch 12 → 13 taken 2391 times.
2391 highest = qMax(highest, group.mid(4).toInt());
187 12634 }
188
1/1
✓ Branch 24 → 25 taken 738 times.
1476 return QStringLiteral("item%1").arg(highest + 1);
189 }
190
191 27698 int labelIndex(const ICal::Component &card, const QString &group) {
192
2/2
✓ Branch 3 → 4 taken 27691 times.
✓ Branch 3 → 5 taken 7 times.
27698 if (group.isEmpty())
193 27691 return -1;
194
1/2
✓ Branch 25 → 6 taken 74 times.
✗ Branch 25 → 26 not taken.
74 for (int i = 0; i < card.properties.size(); ++i)
195
11/16
✓ Branch 7 → 8 taken 74 times.
✓ Branch 9 → 10 taken 14 times.
✓ Branch 9 → 16 taken 60 times.
✓ Branch 12 → 13 taken 14 times.
✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 16 taken 7 times.
✓ Branch 17 → 18 taken 14 times.
✓ Branch 17 → 19 taken 60 times.
✓ Branch 19 → 20 taken 74 times.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 7 times.
✓ Branch 21 → 23 taken 67 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
74 if (groupOf(card.properties.at(i)) == group && baseName(card.properties.at(i)) == QLatin1String("X-ABLABEL"))
196 7 return i;
197 ✗ return -1;
198 }
199
200 ✗ QString labelText(const ICal::Component &card, int index) {
201 ✗ const int at = labelIndex(card, groupOf(card.properties.at(index)));
202 ✗ if (at < 0)
203 ✗ return {};
204 ✗ QString label = ICal::unescapeText(card.properties.at(at).value);
205 ✗ if (label.startsWith(QLatin1String("_$!<")) && label.endsWith(QLatin1String(">!$_")))
206 ✗ label = label.mid(4, label.size() - 8);
207 ✗ return label;
208 ✗ }
209
210 // Gives the property at `index` the label; a new label line goes directly
211 // behind the property (Apple's layout), a cleared label removes its line.
212 27692 void setLabel(Writer *w, int index, const QString &label) {
213
1/1
✓ Branch 3 → 4 taken 27692 times.
27692 QString group = groupOf(w->card.properties.at(index));
214
1/1
✓ Branch 4 → 5 taken 27692 times.
27692 const int existing = labelIndex(w->card, group);
215
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 20 taken 27691 times.
27692 if (existing >= 0) {
216
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (label.isEmpty())
217 ✗ w->removeAt(existing);
218
2/3
✓ Branch 10 → 11 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 19 not taken.
1 else if (ICal::unescapeText(w->card.properties.at(existing).value) != label) {
219
3/3
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
1 w->card.properties[existing].setValue(ICal::escapeText(label));
220 1 w->changed = true;
221 }
222 1 return;
223 }
224
2/2
✓ Branch 21 → 22 taken 27690 times.
✓ Branch 21 → 23 taken 1 time.
27691 if (label.isEmpty())
225 27690 return;
226
1/2
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 39 not taken.
1 if (group.isEmpty()) {
227
1/1
✓ Branch 25 → 26 taken 1 time.
1 group = freeGroup(w->card);
228
1/1
✓ Branch 28 → 29 taken 1 time.
1 ICal::Property &property = w->card.properties[index];
229
3/3
✓ Branch 29 → 30 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
1 property.name = group + QLatin1Char('.') + baseName(property);
230 1 property.originalLine.clear();
231 }
232 1 ICal::Property created;
233
1/1
✓ Branch 42 → 43 taken 1 time.
1 created.name = group + QStringLiteral(".X-ABLabel");
234
1/1
✓ Branch 47 → 48 taken 1 time.
1 created.value = ICal::escapeText(label);
235
1/1
✓ Branch 50 → 51 taken 1 time.
1 w->insert(index + 1, created);
236 27692 }
237
238 27694 void setTypes(ICal::Property *property, const QStringList &types, bool preferred, bool v4) {
239 27694 property->parameters.removeIf([](const ICal::Parameter &parameter) {
240
3/6
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 13 taken 2 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 13 taken 1 time.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 14 not taken.
3 return parameter.name == QLatin1String("TYPE") || parameter.name == QLatin1String("PREF") ||
241
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
3 (parameter.value.isNull() && parameter.name != QLatin1String("ENCODING"));
242 });
243
2/2
✓ Branch 17 → 5 taken 4497 times.
✓ Branch 17 → 18 taken 27694 times.
32191 for (const QString &type : types)
244 8994 property->parameters.append({QStringLiteral("TYPE"), v4 ? type.toLower() : type.toUpper()});
245
2/2
✓ Branch 18 → 19 taken 4 times.
✓ Branch 18 → 42 taken 27690 times.
27694 if (preferred) {
246
2/2
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 31 taken 3 times.
4 if (v4)
247 2 property->parameters.append({QStringLiteral("PREF"), QStringLiteral("1")});
248 else
249 6 property->parameters.append({QStringLiteral("TYPE"), QStringLiteral("pref")});
250 }
251 27694 property->originalLine.clear();
252 32199 }
253
254 // One edited value in wire form, with what parse() saw for its base line.
255 struct Entry {
256 QString wire;
257 QStringList types;
258 bool preferred = false;
259 QString label;
260 int sourceIndex = -1;
261 };
262
263 69 QString currentWire(const ICal::Property &property, const QString &name) {
264
7/7
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 9 taken 65 times.
✓ Branch 6 → 7 taken 4 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 3 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 68 times.
69 if (name == QLatin1String("TEL") && property.value.startsWith(QLatin1String("tel:"), Qt::CaseInsensitive))
265 1 return property.value.mid(4);
266 68 return property.value;
267 }
268
269 69 void updateKept(Writer *w, const QString &name, const Entry &entry, const Entry &old) {
270 69 const int index = w->currentIndex(entry.sourceIndex);
271 69 ICal::Property &property = w->card.properties[index];
272
3/3
✓ Branch 4 → 5 taken 69 times.
✓ Branch 7 → 8 taken 45 times.
✓ Branch 7 → 42 taken 24 times.
69 if (currentWire(property, name) != entry.wire) {
273
4/4
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 22 taken 43 times.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 21 taken 1 time.
48 const bool uri = name == QLatin1String("TEL") &&
274
5/11
✓ Branch 14 → 15 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 25 → 27 taken 43 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 29 taken 43 times.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
47 (property.parameter(QStringLiteral("VALUE")).compare(QLatin1String("uri"), Qt::CaseInsensitive) == 0 ||
275
4/5
✓ Branch 19 → 20 taken 1 time.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 1 time.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 43 times.
46 property.value.startsWith(QLatin1String("tel:"), Qt::CaseInsensitive));
276
8/12
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 34 taken 44 times.
✓ Branch 33 → 35 taken 1 time.
✓ Branch 35 → 36 taken 45 times.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 44 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 41 taken 44 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
46 property.setValue(uri ? QStringLiteral("tel:") + entry.wire : entry.wire);
277 45 w->changed = true;
278 }
279
5/6
✓ Branch 43 → 44 taken 69 times.
✗ Branch 43 → 45 not taken.
✓ Branch 44 → 45 taken 3 times.
✓ Branch 44 → 46 taken 66 times.
✓ Branch 47 → 48 taken 3 times.
✓ Branch 47 → 50 taken 66 times.
69 if (entry.types != old.types || entry.preferred != old.preferred) {
280 3 setTypes(&property, entry.types, entry.preferred, w->v4);
281 3 w->changed = true;
282 }
283
2/2
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 68 times.
69 if (entry.label != old.label)
284 1 setLabel(w, index, entry.label);
285 69 }
286
287 32 void removeWithGroup(Writer *w, int index) {
288
1/1
✓ Branch 3 → 4 taken 32 times.
32 const QString group = groupOf(w->card.properties.at(index));
289
1/1
✓ Branch 4 → 5 taken 32 times.
32 w->removeAt(index);
290
2/2
✓ Branch 6 → 7 taken 20 times.
✓ Branch 6 → 8 taken 12 times.
32 if (group.isEmpty())
291 20 return;
292
4/4
✓ Branch 8 → 9 taken 12 times.
✓ Branch 9 → 10 taken 12 times.
✓ Branch 35 → 11 taken 165 times.
✓ Branch 35 → 36 taken 12 times.
177 for (const ICal::Property &property : w->card.properties)
293
7/14
✓ Branch 12 → 13 taken 165 times.
✓ Branch 14 → 15 taken 12 times.
✓ Branch 14 → 24 taken 153 times.
✓ Branch 16 → 17 taken 12 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 24 taken 12 times.
✓ Branch 29 → 30 taken 165 times.
✗ Branch 29 → 31 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 165 times.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
330 if (groupOf(property) == group && baseName(property) != QLatin1String("X-ABLABEL") &&
294
3/10
✗ Branch 19 → 20 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 165 times.
✓ Branch 27 → 28 taken 12 times.
✓ Branch 27 → 29 taken 153 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
165 !baseName(property).startsWith(QLatin1String("X-AB")))
295 ✗ return;
296
2/2
✓ Branch 2 → 3 taken 165 times.
✓ Branch 36 → 37 taken 12 times.
177 w->removeWhere([&group](const ICal::Property &property) { return groupOf(property) == group; });
297 32 }
298
299 // Applies the edited values of one property kind: kept lines are updated in
300 // place, missing ones removed (with their label group), new ones inserted
301 // behind the last line of the kind.
302 27585 void applyValues(Writer *w, const QString &name, const QList<Entry> &entries, const QList<Entry> &before) {
303 27585 QSet<int> kept;
304
2/2
✓ Branch 25 → 5 taken 27760 times.
✓ Branch 25 → 26 taken 27585 times.
55345 for (const Entry &entry : entries) {
305
5/6
✓ Branch 6 → 7 taken 71 times.
✓ Branch 6 → 9 taken 27689 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 71 times.
✓ Branch 11 → 12 taken 27689 times.
✓ Branch 11 → 13 taken 71 times.
27760 if (entry.sourceIndex < 0 || w->currentIndex(entry.sourceIndex) < 0)
306 27689 continue;
307
2/2
✓ Branch 21 → 15 taken 109 times.
✓ Branch 21 → 22 taken 71 times.
180 for (const Entry &old : before)
308
2/2
✓ Branch 16 → 17 taken 69 times.
✓ Branch 16 → 19 taken 40 times.
109 if (old.sourceIndex == entry.sourceIndex) {
309
1/1
✓ Branch 17 → 18 taken 69 times.
69 kept.insert(entry.sourceIndex);
310
1/1
✓ Branch 18 → 19 taken 69 times.
69 updateKept(w, name, entry, old);
311 }
312 }
313
2/2
✓ Branch 39 → 28 taken 73 times.
✓ Branch 39 → 40 taken 27585 times.
27658 for (const Entry &old : before) {
314 73 const int index = w->currentIndex(old.sourceIndex);
315
5/6
✓ Branch 31 → 32 taken 4 times.
✓ Branch 31 → 34 taken 69 times.
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 34 not taken.
✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 37 taken 69 times.
73 if (!kept.contains(old.sourceIndex) && index >= 0)
316
1/1
✓ Branch 36 → 37 taken 4 times.
4 removeWithGroup(w, index);
317 }
318 27585 int insertAt = -1;
319
2/2
✓ Branch 49 → 41 taken 87562 times.
✓ Branch 49 → 50 taken 27585 times.
115147 for (int i = 0; i < w->card.properties.size(); ++i)
320
3/3
✓ Branch 42 → 43 taken 87562 times.
✓ Branch 45 → 46 taken 69 times.
✓ Branch 45 → 47 taken 87493 times.
87562 if (baseName(w->card.properties.at(i)) == name)
321 69 insertAt = i + 1;
322
2/2
✓ Branch 50 → 51 taken 27528 times.
✓ Branch 50 → 53 taken 57 times.
27585 if (insertAt < 0)
323 27528 insertAt = int(w->card.properties.size());
324
2/2
✓ Branch 78 → 55 taken 27760 times.
✓ Branch 78 → 79 taken 27585 times.
55345 for (const Entry &entry : entries) {
325
7/8
✓ Branch 56 → 57 taken 71 times.
✓ Branch 56 → 59 taken 27689 times.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 61 taken 69 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 27691 times.
✓ Branch 63 → 64 taken 69 times.
✓ Branch 63 → 65 taken 27691 times.
27760 if ((entry.sourceIndex >= 0 && kept.contains(entry.sourceIndex)) || entry.wire.isEmpty())
326 69 continue;
327 27691 ICal::Property created;
328 27691 created.name = name;
329 27691 created.value = entry.wire;
330
1/1
✓ Branch 67 → 68 taken 27691 times.
27691 setTypes(&created, entry.types, entry.preferred, w->v4);
331
1/1
✓ Branch 68 → 69 taken 27691 times.
27691 w->insert(insertAt, created);
332
1/1
✓ Branch 69 → 70 taken 27691 times.
27691 setLabel(w, insertAt, entry.label);
333
2/2
✓ Branch 71 → 72 taken 27690 times.
✓ Branch 71 → 73 taken 1 time.
27691 insertAt += entry.label.isEmpty() ? 1 : 2;
334 27691 }
335 27585 }
336
337 // F-28 (SPRINT-104): TEL and URL are SINGLE values, not text lists — a
338 // comma or semicolon in them has no structural meaning and must reach the
339 // wire verbatim (the blanket escapeText rewrote
340 // "…?q=52.5,13.4;z=12" into "…?q=52.5\,13.4\;z=12" and
341 // "+49 30 1234;ext=56" gained backslashes; URI consumers saw them).
342 158712 QList<Entry> entriesOf(const QList<ContactValue> &values, bool verbatim) {
343 158712 QList<Entry> entries;
344
2/2
✓ Branch 17 → 4 taken 29597 times.
✓ Branch 17 → 18 taken 158712 times.
188309 for (const ContactValue &value : values)
345 29597 entries.append({verbatim ? value.value.trimmed()
346
3/5
✓ Branch 7 → 8 taken 26829 times.
✓ Branch 13 → 14 taken 26829 times.
✓ Branch 13 → 15 taken 2768 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
56426 : ICal::escapeText(value.value.trimmed()),
347 59194 value.types, value.preferred, value.label,
348 29597 value.sourceIndex});
349 158712 return entries;
350 56426 }
351
352 52904 QList<Entry> entriesOf(const QList<ContactAddress> &addresses) {
353 52904 QList<Entry> entries;
354
2/2
✓ Branch 28 → 4 taken 3053 times.
✓ Branch 28 → 29 taken 52904 times.
55957 for (const ContactAddress &a : addresses) {
355
3/3
✓ Branch 5 → 6 taken 3053 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 3052 times.
3053 if (a.isEmpty())
356 1 continue;
357
2/4
✓ Branch 23 → 24 taken 21364 times.
✓ Branch 23 → 25 taken 3052 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
24416 entries.append({escapeComponents({a.poBox, a.extended, a.street, a.locality, a.region, a.postalCode, a.country}),
358 9156 a.types, a.preferred, a.label, a.sourceIndex});
359 }
360 52904 return entries;
361 3052 }
362
363 105808 bool sameEntries(const QList<Entry> &a, const QList<Entry> &b) {
364
2/2
✓ Branch 4 → 5 taken 27536 times.
✓ Branch 4 → 6 taken 78272 times.
105808 if (a.size() != b.size())
365 27536 return false;
366
2/2
✓ Branch 31 → 7 taken 2458 times.
✓ Branch 31 → 32 taken 78223 times.
80681 for (int i = 0; i < a.size(); ++i)
367
3/4
✓ Branch 14 → 15 taken 2411 times.
✗ Branch 14 → 25 not taken.
✓ Branch 17 → 18 taken 2409 times.
✓ Branch 17 → 25 taken 2 times.
4869 if (a.at(i).wire != b.at(i).wire || a.at(i).types != b.at(i).types || a.at(i).preferred != b.at(i).preferred ||
368
6/8
✓ Branch 10 → 11 taken 2411 times.
✓ Branch 10 → 25 taken 47 times.
✓ Branch 21 → 22 taken 2409 times.
✗ Branch 21 → 25 not taken.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 2409 times.
✓ Branch 27 → 28 taken 49 times.
✓ Branch 27 → 29 taken 2409 times.
4869 a.at(i).label != b.at(i).label || a.at(i).sourceIndex != b.at(i).sourceIndex)
369 49 return false;
370 78223 return true;
371 }
372
373 // ─── whole-property replacements ───
374
375 303 void setCategories(Writer *w, const QStringList &categories) {
376
1/1
✓ Branch 5 → 6 taken 303 times.
303 int position = indexOfName(w->card, QStringLiteral("CATEGORIES"));
377
2/2
✓ Branch 3 → 4 taken 2495 times.
✓ Branch 8 → 9 taken 303 times.
2798 w->removeWhere([](const ICal::Property &p) { return baseName(p) == QLatin1String("CATEGORIES"); });
378 303 w->changed = true;
379
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 302 times.
303 if (categories.isEmpty())
380 1 return;
381 302 QStringList escaped;
382
2/2
✓ Branch 20 → 14 taken 323 times.
✓ Branch 20 → 21 taken 302 times.
625 for (const QString &category : categories)
383
2/2
✓ Branch 15 → 16 taken 323 times.
✓ Branch 16 → 17 taken 323 times.
323 escaped.append(ICal::escapeText(category));
384 302 ICal::Property created;
385 302 created.name = QStringLiteral("CATEGORIES");
386
1/1
✓ Branch 29 → 30 taken 302 times.
302 created.value = escaped.join(QLatin1Char(','));
387
2/2
✓ Branch 32 → 33 taken 299 times.
✓ Branch 32 → 35 taken 3 times.
302 if (position < 0)
388 299 position = int(w->card.properties.size());
389
1/1
✓ Branch 35 → 36 taken 302 times.
302 w->insert(position, created);
390 302 }
391
392 51 void setMembers(Writer *w, const QStringList &members) {
393 // D-7: write in the model the card uses (vCard 4 MEMBER or Apple's
394 // X-ADDRESSBOOKSERVER-MEMBER).
395
4/9
✓ Branch 5 → 6 taken 51 times.
✓ Branch 6 → 7 taken 33 times.
✓ Branch 6 → 12 taken 18 times.
✓ Branch 20 → 21 taken 51 times.
✗ Branch 20 → 22 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
186 const bool apple = indexOfName(w->card, QStringLiteral("X-ADDRESSBOOKSERVER-KIND")) >= 0 ||
396
7/13
✓ Branch 10 → 11 taken 33 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 33 times.
✓ Branch 14 → 15 taken 33 times.
✓ Branch 14 → 16 taken 18 times.
✓ Branch 16 → 17 taken 33 times.
✓ Branch 16 → 18 taken 18 times.
✓ Branch 18 → 19 taken 51 times.
✗ Branch 18 → 20 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
84 indexOfName(w->card, QStringLiteral("X-ADDRESSBOOKSERVER-MEMBER")) >= 0;
397
6/6
✓ Branch 22 → 23 taken 18 times.
✓ Branch 22 → 26 taken 33 times.
✓ Branch 29 → 30 taken 33 times.
✓ Branch 29 → 31 taken 18 times.
✓ Branch 31 → 32 taken 18 times.
✓ Branch 31 → 33 taken 33 times.
102 const QString name = apple ? QStringLiteral("X-ADDRESSBOOKSERVER-MEMBER") : QStringLiteral("MEMBER");
398
1/1
✓ Branch 33 → 34 taken 51 times.
51 w->removeWhere([](const ICal::Property &p) {
399
7/14
✓ Branch 3 → 4 taken 309 times.
✓ Branch 5 → 6 taken 309 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 309 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 308 times.
✓ Branch 13 → 14 taken 309 times.
✗ Branch 13 → 15 not taken.
✓ Branch 15 → 16 taken 309 times.
✗ Branch 15 → 17 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
309 return baseName(p) == QLatin1String("MEMBER") || baseName(p) == QLatin1String("X-ADDRESSBOOKSERVER-MEMBER");
400 });
401
2/2
✓ Branch 43 → 36 taken 144 times.
✓ Branch 43 → 44 taken 51 times.
195 for (const QString &member : members) {
402 144 ICal::Property created;
403 144 created.name = name;
404 144 created.value = member;
405
1/1
✓ Branch 39 → 40 taken 144 times.
144 w->append(created);
406 144 }
407 51 w->changed = true;
408 51 }
409
410 333 void setPhoto(Writer *w, const ContactPhoto &photo) {
411
1/1
✓ Branch 5 → 6 taken 333 times.
333 const int position = indexOfName(w->card, QStringLiteral("PHOTO"));
412
2/2
✓ Branch 3 → 4 taken 3145 times.
✓ Branch 8 → 9 taken 333 times.
3478 w->removeWhere([](const ICal::Property &p) { return baseName(p) == QLatin1String("PHOTO"); });
413 333 w->changed = true;
414
2/2
✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 11 taken 330 times.
333 if (photo.kind == ContactPhoto::Kind::None)
415 3 return;
416 330 ICal::Property created;
417 330 created.name = QStringLiteral("PHOTO");
418
4/4
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 22 taken 329 times.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 329 times.
331 const QString mime = photo.mimeType.isEmpty() ? QStringLiteral("image/jpeg") : photo.mimeType;
419
2/2
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 45 taken 329 times.
330 if (photo.kind == ContactPhoto::Kind::Uri) {
420 1 created.value = photo.uri;
421
2/8
✓ Branch 39 → 40 taken 1 time.
✗ Branch 39 → 41 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 1 time.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✗ Branch 119 → 120 not taken.
✗ Branch 119 → 121 not taken.
1 created.parameters.append({QStringLiteral("VALUE"), w->v4 ? QStringLiteral("uri") : QStringLiteral("URI")});
422
2/2
✓ Branch 45 → 46 taken 60 times.
✓ Branch 45 → 69 taken 269 times.
329 } else if (w->v4) {
423
5/5
✓ Branch 47 → 48 taken 60 times.
✓ Branch 48 → 49 taken 60 times.
✓ Branch 55 → 56 taken 60 times.
✓ Branch 56 → 57 taken 60 times.
✓ Branch 57 → 58 taken 60 times.
180 created.value = QStringLiteral("data:") + mime + QStringLiteral(";base64,") + QString::fromLatin1(photo.data.toBase64());
424 } else {
425 269 created.parameters.append({QStringLiteral("ENCODING"), QStringLiteral("b")});
426
1/1
✓ Branch 85 → 86 taken 269 times.
538 created.parameters.append({QStringLiteral("TYPE"), mime.section(QLatin1Char('/'), 1).toUpper()});
427
2/2
✓ Branch 92 → 93 taken 269 times.
✓ Branch 93 → 94 taken 269 times.
269 created.value = QString::fromLatin1(photo.data.toBase64());
428 }
429
3/3
✓ Branch 98 → 99 taken 329 times.
✓ Branch 98 → 101 taken 1 time.
✓ Branch 102 → 103 taken 330 times.
330 w->insert(position < 0 ? int(w->card.properties.size()) : position, created);
430 1408 }
431
432 26452 QString displayNameFor(const ContactCard &card) {
433
3/3
✓ Branch 2 → 3 taken 26452 times.
✓ Branch 5 → 6 taken 24267 times.
✓ Branch 5 → 8 taken 2185 times.
26452 if (!card.formattedName.trimmed().isEmpty())
434
1/1
✓ Branch 6 → 7 taken 24267 times.
24267 return card.formattedName.trimmed();
435 ✗ const QString composed = QStringList{card.name.prefix, card.name.given, card.name.additional, card.name.family,
436
2/4
✓ Branch 21 → 22 taken 10925 times.
✓ Branch 21 → 23 taken 2185 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
13110 card.name.suffix}
437
1/1
✓ Branch 16 → 17 taken 2185 times.
4370 .join(QLatin1Char(' '))
438
1/1
✓ Branch 17 → 18 taken 2185 times.
2185 .simplified();
439
2/2
✓ Branch 24 → 25 taken 2154 times.
✓ Branch 24 → 26 taken 31 times.
2185 if (!composed.isEmpty())
440 2154 return composed;
441
1/2
✓ Branch 27 → 28 taken 31 times.
✗ Branch 27 → 29 not taken.
31 if (!card.organization.isEmpty())
442 31 return card.organization;
443 ✗ return card.emails.isEmpty() ? QString() : card.emails.first().value;
444 4370 }
445
446 26452 void applyFields(Writer *w, const ContactCard &before, const ContactCard &after) {
447
1/1
✓ Branch 2 → 3 taken 26452 times.
26452 const QString fn = displayNameFor(after);
448
10/15
✓ Branch 4 → 5 taken 1378 times.
✓ Branch 4 → 10 taken 25074 times.
✓ Branch 8 → 9 taken 1378 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1378 times.
✓ Branch 12 → 13 taken 1378 times.
✓ Branch 12 → 14 taken 25074 times.
✓ Branch 14 → 15 taken 1378 times.
✓ Branch 14 → 16 taken 25074 times.
✓ Branch 16 → 17 taken 25074 times.
✓ Branch 16 → 26 taken 1378 times.
✗ Branch 208 → 209 not taken.
✗ Branch 208 → 210 not taken.
✗ Branch 212 → 213 not taken.
✗ Branch 212 → 214 not taken.
27830 if (fn != before.formattedName || indexOfName(w->card, QStringLiteral("FN")) < 0)
449
2/2
✓ Branch 17 → 18 taken 25074 times.
✓ Branch 21 → 22 taken 25074 times.
50148 setSingle(w, QStringLiteral("FN"), ICal::escapeText(fn));
450
11/15
✓ Branch 27 → 28 taken 24211 times.
✓ Branch 27 → 33 taken 2241 times.
✓ Branch 31 → 32 taken 24211 times.
✓ Branch 32 → 33 taken 22501 times.
✓ Branch 32 → 34 taken 1710 times.
✓ Branch 35 → 36 taken 24211 times.
✓ Branch 35 → 37 taken 2241 times.
✓ Branch 37 → 38 taken 24211 times.
✓ Branch 37 → 39 taken 2241 times.
✓ Branch 39 → 40 taken 24742 times.
✓ Branch 39 → 59 taken 1710 times.
✗ Branch 225 → 226 not taken.
✗ Branch 225 → 227 not taken.
✗ Branch 229 → 230 not taken.
✗ Branch 229 → 231 not taken.
50663 if (after.name != before.name || indexOfName(w->card, QStringLiteral("N")) < 0)
451
1/1
✓ Branch 50 → 51 taken 24742 times.
24742 setSingle(w, QStringLiteral("N"),
452
3/5
✓ Branch 46 → 47 taken 24742 times.
✓ Branch 56 → 57 taken 123710 times.
✓ Branch 56 → 58 taken 24742 times.
✗ Branch 244 → 245 not taken.
✗ Branch 244 → 246 not taken.
173194 escapeComponents({after.name.family, after.name.given, after.name.additional, after.name.prefix,
453 24742 after.name.suffix}));
454
2/2
✓ Branch 60 → 61 taken 19 times.
✓ Branch 60 → 70 taken 26433 times.
26452 if (after.nickname != before.nickname)
455
2/2
✓ Branch 61 → 62 taken 19 times.
✓ Branch 65 → 66 taken 19 times.
38 setSingle(w, QStringLiteral("NICKNAME"), ICal::escapeText(after.nickname));
456
5/6
✓ Branch 71 → 72 taken 25521 times.
✓ Branch 71 → 74 taken 931 times.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 25521 times.
✓ Branch 76 → 77 taken 931 times.
✓ Branch 76 → 125 taken 25521 times.
26452 if (after.organization != before.organization || after.department != before.department) {
457
2/4
✓ Branch 81 → 82 taken 1862 times.
✓ Branch 81 → 83 taken 931 times.
✗ Branch 265 → 266 not taken.
✗ Branch 265 → 267 not taken.
2793 QStringList parts{after.organization, after.department};
458
1/1
✓ Branch 86 → 87 taken 931 times.
931 const int index = indexOfName(w->card, QStringLiteral("ORG"));
459
2/2
✓ Branch 89 → 90 taken 1 time.
✓ Branch 89 → 105 taken 930 times.
931 if (index >= 0) {
460 // Further organisational units (beyond the department) survive.
461
1/1
✓ Branch 94 → 95 taken 1 time.
1 const QStringList old = w->card.properties.at(index).value.split(QLatin1Char(';'));
462
1/2
✗ Branch 102 → 96 not taken.
✓ Branch 102 → 103 taken 1 time.
1 for (int i = 2; i < old.size(); ++i)
463 ✗ parts.append(ICal::unescapeText(old.at(i)));
464 1 }
465
6/7
✓ Branch 108 → 109 taken 1861 times.
✗ Branch 108 → 113 not taken.
✓ Branch 109 → 110 taken 1861 times.
✓ Branch 111 → 112 taken 930 times.
✓ Branch 111 → 113 taken 931 times.
✓ Branch 114 → 106 taken 930 times.
✓ Branch 114 → 115 taken 931 times.
1861 while (!parts.isEmpty() && parts.last().isEmpty())
466 930 parts.removeLast();
467
2/2
✓ Branch 115 → 116 taken 931 times.
✓ Branch 119 → 120 taken 931 times.
1862 setSingle(w, QStringLiteral("ORG"), escapeComponents(parts));
468 931 }
469
2/2
✓ Branch 126 → 127 taken 367 times.
✓ Branch 126 → 136 taken 26085 times.
26452 if (after.title != before.title)
470
2/2
✓ Branch 127 → 128 taken 367 times.
✓ Branch 131 → 132 taken 367 times.
734 setSingle(w, QStringLiteral("TITLE"), ICal::escapeText(after.title));
471
2/2
✓ Branch 137 → 138 taken 32 times.
✓ Branch 137 → 147 taken 26420 times.
26452 if (after.note != before.note)
472
2/2
✓ Branch 138 → 139 taken 32 times.
✓ Branch 142 → 143 taken 32 times.
64 setSingle(w, QStringLiteral("NOTE"), ICal::escapeText(after.note));
473
2/2
✓ Branch 148 → 149 taken 305 times.
✓ Branch 148 → 156 taken 26147 times.
26452 if (after.birthday != before.birthday)
474
1/1
✓ Branch 152 → 153 taken 305 times.
610 setDate(w, QStringLiteral("BDAY"), after.birthday);
475
2/2
✓ Branch 157 → 158 taken 47 times.
✓ Branch 157 → 178 taken 26405 times.
26452 if (after.anniversary != before.anniversary) {
476
1/1
✓ Branch 161 → 162 taken 47 times.
47 const bool x = indexOfName(w->card, QStringLiteral("X-ANNIVERSARY")) >= 0;
477
4/11
✗ Branch 164 → 165 not taken.
✓ Branch 164 → 168 taken 47 times.
✓ Branch 171 → 172 taken 47 times.
✓ Branch 173 → 174 taken 47 times.
✗ Branch 173 → 175 not taken.
✗ Branch 175 → 176 not taken.
✓ Branch 175 → 177 taken 47 times.
✗ Branch 333 → 334 not taken.
✗ Branch 333 → 335 not taken.
✗ Branch 337 → 338 not taken.
✗ Branch 337 → 339 not taken.
94 setDate(w, x ? QStringLiteral("X-ANNIVERSARY") : QStringLiteral("ANNIVERSARY"), after.anniversary);
478 }
479
3/3
✓ Branch 178 → 179 taken 26452 times.
✓ Branch 179 → 180 taken 303 times.
✓ Branch 179 → 181 taken 26149 times.
26452 if (after.categories != before.categories)
480
1/1
✓ Branch 180 → 181 taken 303 times.
303 setCategories(w, after.categories);
481
3/3
✓ Branch 181 → 182 taken 26452 times.
✓ Branch 182 → 183 taken 51 times.
✓ Branch 182 → 184 taken 26401 times.
26452 if (after.members != before.members)
482
1/1
✓ Branch 183 → 184 taken 51 times.
51 setMembers(w, after.members);
483
2/2
✓ Branch 186 → 187 taken 10 times.
✓ Branch 186 → 203 taken 26442 times.
26452 if (after.isFamily() != before.isFamily()) // X-MAILJD-KIND:family, both versions (ContactCard::isFamily)
484
5/7
✓ Branch 188 → 189 taken 8 times.
✓ Branch 188 → 192 taken 2 times.
✓ Branch 196 → 197 taken 10 times.
✓ Branch 200 → 201 taken 8 times.
✓ Branch 200 → 202 taken 2 times.
✗ Branch 350 → 351 not taken.
✗ Branch 350 → 352 not taken.
28 setSingle(w, QStringLiteral("X-MAILJD-KIND"), after.isFamily() ? QStringLiteral("family") : QString());
485
2/2
✓ Branch 204 → 205 taken 333 times.
✓ Branch 204 → 206 taken 26119 times.
26452 if (after.photo != before.photo)
486
1/1
✓ Branch 205 → 206 taken 333 times.
333 setPhoto(w, after.photo);
487 52125 }
488
489 26452 void applyMultiValues(Writer *w, const ContactCard &before, const ContactCard &after) {
490 const QList<QPair<QString, QPair<QList<Entry>, QList<Entry>>>> kinds{
491
2/2
✓ Branch 5 → 6 taken 26452 times.
✓ Branch 6 → 7 taken 26452 times.
79356 {QStringLiteral("EMAIL"), {entriesOf(before.emails, false), entriesOf(after.emails, false)}},
492
2/2
✓ Branch 12 → 13 taken 26452 times.
✓ Branch 13 → 14 taken 26452 times.
79356 {QStringLiteral("TEL"), {entriesOf(before.phones, true), entriesOf(after.phones, true)}},
493
2/2
✓ Branch 19 → 20 taken 26452 times.
✓ Branch 20 → 21 taken 26452 times.
79356 {QStringLiteral("URL"), {entriesOf(before.urls, true), entriesOf(after.urls, true)}},
494
4/6
✓ Branch 26 → 27 taken 26452 times.
✓ Branch 27 → 28 taken 26452 times.
✓ Branch 32 → 33 taken 105808 times.
✓ Branch 32 → 34 taken 26452 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
370328 {QStringLiteral("ADR"), {entriesOf(before.addresses), entriesOf(after.addresses)}}};
495
2/2
✓ Branch 62 → 56 taken 105808 times.
✓ Branch 62 → 63 taken 26452 times.
132260 for (const auto &kind : kinds)
496
3/3
✓ Branch 57 → 58 taken 105808 times.
✓ Branch 58 → 59 taken 27585 times.
✓ Branch 58 → 60 taken 78223 times.
105808 if (!sameEntries(kind.second.first, kind.second.second))
497
1/1
✓ Branch 59 → 60 taken 27585 times.
27585 applyValues(w, kind.first, kind.second.second, kind.second.first);
498 158712 }
499
500 26372 void touchRevision(Writer *w, const QDateTime &now) {
501
5/5
✓ Branch 2 → 3 taken 26372 times.
✓ Branch 3 → 4 taken 1485 times.
✓ Branch 3 → 5 taken 24887 times.
✓ Branch 5 → 6 taken 24887 times.
✓ Branch 6 → 7 taken 26372 times.
26372 const QDateTime stamp = (now.isValid() ? now : QDateTime::currentDateTimeUtc()).toUTC();
502
2/2
✓ Branch 11 → 12 taken 26372 times.
✓ Branch 15 → 16 taken 26372 times.
52744 setSingle(w, QStringLiteral("REV"), stamp.toString(QStringLiteral("yyyyMMdd'T'HHmmss'Z'")));
503
1/1
✓ Branch 24 → 25 taken 26372 times.
26372 const int index = indexOfName(w->card, QStringLiteral("REV"));
504
1/2
✓ Branch 27 → 28 taken 26372 times.
✗ Branch 27 → 36 not taken.
26372 if (index >= 0)
505
2/2
✓ Branch 28 → 29 taken 26372 times.
✓ Branch 32 → 33 taken 26372 times.
52744 w->card.properties[index].removeParameter(QStringLiteral("VALUE"));
506 26372 }
507
508 } // namespace detail
509
510 using namespace detail;
511
512 26464 QString patch(const QString &base, const ContactCard &edited, QString *error, const QDateTime &now) {
513
2/3
✓ Branch 2 → 3 taken 26464 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 14 taken 26464 times.
26464 if (base.trimmed().isEmpty())
514 ✗ return create(edited, QStringLiteral("3.0"), now);
515 26464 Writer w;
516 26464 int skipped = 0;
517
2/3
✓ Branch 14 → 15 taken 26464 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 26464 times.
26464 if (!parseTree(base, &w.card, error, &skipped))
518 ✗ return {};
519
2/2
✓ Branch 21 → 18 taken 106016 times.
✓ Branch 21 → 22 taken 26464 times.
132480 for (int i = 0; i < w.card.properties.size(); ++i)
520
1/1
✓ Branch 18 → 19 taken 106016 times.
106016 w.origin.append(i);
521
2/2
✓ Branch 22 → 23 taken 12 times.
✓ Branch 22 → 32 taken 26452 times.
26464 if (skipped > 0) {
522
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 31 taken 11 times.
12 if (error)
523 1 *error = QStringLiteral("The stored card has lines MailJD cannot read; it is not rewritten");
524 12 return {};
525 }
526
1/1
✓ Branch 32 → 33 taken 26452 times.
26452 ContactCard before;
527
2/3
✓ Branch 33 → 34 taken 26452 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 26452 times.
26452 if (!parse(base, &before, error))
528 ✗ return {};
529
2/2
✓ Branch 38 → 39 taken 34 times.
✓ Branch 38 → 41 taken 26418 times.
26452 if (before.version == QLatin1String("2.1")) {
530
1/1
✓ Branch 39 → 40 taken 34 times.
34 upgradeTo30(&w.card, &w.origin); // same properties in the same order: indices hold
531 34 w.changed = true;
532 }
533 26452 w.v4 = before.version == QLatin1String("4.0");
534
1/1
✓ Branch 43 → 44 taken 26452 times.
26452 applyMultiValues(&w, before, edited);
535
1/1
✓ Branch 44 → 45 taken 26452 times.
26452 applyFields(&w, before, edited);
536
1/1
✓ Branch 45 → 46 taken 26452 times.
26452 applyPeople(&w, before, edited); // Sprint 103 (VCardPeoplePatch.cpp)
537
2/2
✓ Branch 46 → 47 taken 80 times.
✓ Branch 46 → 49 taken 26372 times.
26452 if (!w.changed)
538
1/1
✓ Branch 47 → 48 taken 80 times.
80 return ICal::serialize(w.card);
539
1/1
✓ Branch 49 → 50 taken 26372 times.
26372 touchRevision(&w, now);
540
1/1
✓ Branch 50 → 51 taken 26372 times.
26372 return ICal::serialize(w.card);
541 26464 }
542
543 24523 QString create(const ContactCard &card, const QString &version, const QDateTime &now) {
544 24523 const bool v4 = version == QLatin1String("4.0");
545
7/11
✓ Branch 11 → 12 taken 519 times.
✓ Branch 11 → 15 taken 24004 times.
✓ Branch 21 → 22 taken 24523 times.
✓ Branch 31 → 32 taken 24004 times.
✓ Branch 31 → 33 taken 519 times.
✓ Branch 33 → 34 taken 519 times.
✓ Branch 33 → 35 taken 24004 times.
✗ Branch 164 → 165 not taken.
✗ Branch 164 → 166 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 168 → 170 not taken.
98092 QString skeleton = QStringLiteral("BEGIN:VCARD\r\nVERSION:") + (v4 ? QStringLiteral("4.0") : QStringLiteral("3.0")) +
546
3/3
✓ Branch 7 → 8 taken 24523 times.
✓ Branch 22 → 23 taken 24523 times.
✓ Branch 23 → 24 taken 24523 times.
122615 QStringLiteral("\r\nPRODID:-//MailJD//MailJD Contacts//EN\r\nUID:") + ICal::escapeText(card.uid) +
547
1/1
✓ Branch 24 → 25 taken 24523 times.
73569 QStringLiteral("\r\n");
548
2/2
✓ Branch 41 → 42 taken 52 times.
✓ Branch 41 → 56 taken 24471 times.
24523 if (card.isGroup())
549
7/11
✓ Branch 42 → 43 taken 34 times.
✓ Branch 42 → 46 taken 18 times.
✓ Branch 49 → 50 taken 52 times.
✓ Branch 51 → 52 taken 18 times.
✓ Branch 51 → 53 taken 34 times.
✓ Branch 53 → 54 taken 34 times.
✓ Branch 53 → 55 taken 18 times.
✗ Branch 193 → 194 not taken.
✗ Branch 193 → 195 not taken.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
104 skeleton += v4 ? QStringLiteral("KIND:group\r\n") : QStringLiteral("X-ADDRESSBOOKSERVER-KIND:group\r\n");
550
4/6
✓ Branch 56 → 57 taken 485 times.
✓ Branch 56 → 61 taken 23986 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 485 times.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 70 taken 24471 times.
24471 else if (v4 && card.kind == QLatin1String("org"))
551 ✗ skeleton += QStringLiteral("KIND:org\r\n");
552
1/1
✓ Branch 73 → 74 taken 24523 times.
24523 skeleton += QStringLiteral("END:VCARD\r\n");
553 24523 ContactCard fresh = card;
554
2/2
✓ Branch 87 → 79 taken 73569 times.
✓ Branch 87 → 88 taken 24523 times.
98092 for (auto *values : {&fresh.emails, &fresh.phones, &fresh.urls})
555
4/4
✓ Branch 79 → 80 taken 73569 times.
✓ Branch 80 → 81 taken 73569 times.
✓ Branch 85 → 82 taken 25608 times.
✓ Branch 85 → 86 taken 73569 times.
99177 for (ContactValue &value : *values)
556 25608 value.sourceIndex = -1;
557
4/4
✓ Branch 88 → 89 taken 24523 times.
✓ Branch 89 → 90 taken 24523 times.
✓ Branch 94 → 91 taken 2062 times.
✓ Branch 94 → 95 taken 24523 times.
26585 for (ContactAddress &address : fresh.addresses)
558 2062 address.sourceIndex = -1;
559
4/4
✓ Branch 95 → 96 taken 24523 times.
✓ Branch 96 → 97 taken 24523 times.
✓ Branch 101 → 98 taken 410 times.
✓ Branch 101 → 102 taken 24523 times.
24933 for (ContactRelation &relation : fresh.relations)
560 410 relation.sourceIndex = -1;
561
4/4
✓ Branch 102 → 103 taken 24523 times.
✓ Branch 103 → 104 taken 24523 times.
✓ Branch 108 → 105 taken 48 times.
✓ Branch 108 → 109 taken 24523 times.
24571 for (ContactPersonRef &ref : fresh.met.via)
562 48 ref.sourceIndex = -1;
563
4/4
✓ Branch 109 → 110 taken 24523 times.
✓ Branch 110 → 111 taken 24523 times.
✓ Branch 115 → 112 taken 131 times.
✓ Branch 115 → 116 taken 24523 times.
24654 for (ContactSocial &social : fresh.social)
564 131 social.sourceIndex = -1;
565
4/4
✓ Branch 116 → 117 taken 24523 times.
✓ Branch 117 → 118 taken 24523 times.
✓ Branch 122 → 119 taken 20 times.
✓ Branch 122 → 123 taken 24523 times.
24543 for (ContactDate &date : fresh.otherDates)
566 20 date.sourceIndex = -1;
567
4/4
✓ Branch 123 → 124 taken 24523 times.
✓ Branch 124 → 125 taken 24523 times.
✓ Branch 129 → 126 taken 10 times.
✓ Branch 129 → 130 taken 24523 times.
24533 for (ContactMailLink &link : fresh.mailLinks)
568 10 link.sourceIndex = -1;
569 24523 QString error;
570
1/1
✓ Branch 130 → 131 taken 24523 times.
24523 const QString result = patch(skeleton, fresh, &error, now);
571
1/2
✗ Branch 132 → 133 not taken.
✓ Branch 132 → 145 taken 24523 times.
24523 if (result.isEmpty())
572 ✗ qCWarning(lcVCard) << "Creating a card failed:" << error;
573 24523 return result;
574 24523 }
575
576 } // namespace VCardCodec
577