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: 96.9% 246 / 0 / 254
Functions: 95.5% 21 / 0 / 22
Branches: 85.5% 342 / 0 / 400

data/SettingsMergePlan.cpp
Line Branch Exec Source
1 #include "SettingsMergePlan.h"
2
3 #include <QJsonArray>
4 #include <QJsonDocument>
5 #include <QJsonObject>
6 #include <iterator>
7
8 namespace SettingsMergePlan {
9
10 namespace {
11
12 133 QString json(const SyncPayload &payload) {
13
4/4
✓ Branch 2 → 3 taken 133 times.
✓ Branch 3 → 4 taken 133 times.
✓ Branch 4 → 5 taken 133 times.
✓ Branch 5 → 6 taken 133 times.
133 return QString::fromUtf8(QJsonDocument(payload.toJson()).toJson(QJsonDocument::Compact));
14 }
15
16 53 SyncPayload payloadOf(const QString &text) {
17
4/4
✓ Branch 2 → 3 taken 53 times.
✓ Branch 3 → 4 taken 53 times.
✓ Branch 4 → 5 taken 53 times.
✓ Branch 5 → 6 taken 53 times.
53 return SyncPayload::fromJson(QJsonDocument::fromJson(text.toUtf8()).object());
18 }
19
20 31 QString listText(const QStringList &values) {
21
4/4
✓ Branch 2 → 3 taken 31 times.
✓ Branch 3 → 4 taken 31 times.
✓ Branch 4 → 5 taken 31 times.
✓ Branch 5 → 6 taken 31 times.
31 return QString::fromUtf8(QJsonDocument(QJsonArray::fromStringList(values)).toJson(QJsonDocument::Compact));
22 }
23
24 // Account ids come from a sync message and may contain ':', the separator
25 // of the item keys ("davAccounts:<id>", "davAccounts:<id>:books"): in a
26 // key '%' and ':' of the id are escaped, so every id maps to its own keys.
27 48 QString keyOfAccount(const QString &id) {
28 48 QString key = id;
29
1/1
✓ Branch 8 → 9 taken 48 times.
48 key.replace(QLatin1Char('%'), QStringLiteral("%25"));
30
1/1
✓ Branch 16 → 17 taken 48 times.
48 key.replace(QLatin1Char(':'), QStringLiteral("%3A"));
31 48 return key;
32 ✗ }
33
34 22 QString accountOfKey(const QString &key) {
35 22 QString id = key;
36
1/1
✓ Branch 9 → 10 taken 22 times.
44 id.replace(QStringLiteral("%3A"), QStringLiteral(":"));
37
1/1
✓ Branch 20 → 21 taken 22 times.
44 id.replace(QStringLiteral("%25"), QStringLiteral("%"));
38 22 return id;
39 ✗ }
40
41 64 const SyncPayload::CardDavAccount *accountOf(const SyncPayload &payload, const QString &id) {
42
2/2
✓ Branch 10 → 4 taken 60 times.
✓ Branch 10 → 11 taken 21 times.
81 for (const SyncPayload::CardDavAccount &account : payload.carddavAccounts)
43
2/2
✓ Branch 6 → 7 taken 43 times.
✓ Branch 6 → 8 taken 17 times.
60 if (account.id == id)
44 43 return &account;
45 21 return nullptr;
46 }
47
48 46 const SyncPayload::CalDavSyncConfig *configOf(const SyncPayload &payload, const QString &id) {
49
2/2
✓ Branch 10 → 4 taken 44 times.
✓ Branch 10 → 11 taken 5 times.
49 for (const SyncPayload::CalDavSyncConfig &config : payload.caldavConfigs)
50
2/2
✓ Branch 6 → 7 taken 41 times.
✓ Branch 6 → 8 taken 3 times.
44 if (config.carddavAccountId == id)
51 41 return &config;
52 5 return nullptr;
53 }
54
55 33 QString configText(const SyncPayload::CalDavSyncConfig *config) {
56
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 31 times.
33 if (!config)
57 2 return {};
58
1/1
✓ Branch 4 → 5 taken 31 times.
31 QJsonObject object;
59
2/2
✓ Branch 5 → 6 taken 31 times.
✓ Branch 10 → 11 taken 31 times.
62 object.insert(QStringLiteral("selected"), QJsonArray::fromStringList(config->selectedCalendars));
60
2/2
✓ Branch 15 → 16 taken 31 times.
✓ Branch 20 → 21 taken 31 times.
62 object.insert(QStringLiteral("readOnly"), QJsonArray::fromStringList(config->readOnlyCalendars));
61
3/3
✓ Branch 25 → 26 taken 31 times.
✓ Branch 26 → 27 taken 31 times.
✓ Branch 27 → 28 taken 31 times.
31 return QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Compact));
62 31 }
63
64 6 QString accountText(const SyncPayload::CardDavAccount &account) {
65
1/1
✓ Branch 2 → 3 taken 6 times.
6 QJsonObject object;
66
2/2
✓ Branch 3 → 4 taken 6 times.
✓ Branch 7 → 8 taken 6 times.
12 object.insert(QStringLiteral("serverUrl"), account.serverUrl);
67
2/2
✓ Branch 11 → 12 taken 6 times.
✓ Branch 15 → 16 taken 6 times.
12 object.insert(QStringLiteral("username"), account.username);
68
2/2
✓ Branch 19 → 20 taken 6 times.
✓ Branch 24 → 25 taken 6 times.
12 object.insert(QStringLiteral("selectedBooks"), QJsonArray::fromStringList(account.selectedBooks));
69
3/3
✓ Branch 29 → 30 taken 6 times.
✓ Branch 30 → 31 taken 6 times.
✓ Branch 31 → 32 taken 6 times.
12 return QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Compact));
70 6 }
71
72 // Identities and templates as item values: compact JSON (keys sorted).
73 66 QMap<QString, QString> textMap(const QMap<QString, QJsonObject> &objects) {
74 66 QMap<QString, QString> out;
75
4/4
✓ Branch 2 → 3 taken 66 times.
✓ Branch 14 → 15 taken 86 times.
✓ Branch 16 → 4 taken 20 times.
✓ Branch 16 → 17 taken 66 times.
86 for (auto it = objects.constBegin(); it != objects.constEnd(); ++it)
76
4/4
✓ Branch 5 → 6 taken 20 times.
✓ Branch 6 → 7 taken 20 times.
✓ Branch 7 → 8 taken 20 times.
✓ Branch 9 → 10 taken 20 times.
20 out.insert(it.key(), QString::fromUtf8(QJsonDocument(it.value()).toJson(QJsonDocument::Compact)));
77 66 return out;
78 ✗ }
79
80 struct Builder {
81 Merge::Plan *plan;
82 bool threeWay;
83
84 177 void add(const QString &id, Merge::Kind kind, Merge::Value base, Merge::Value local, Merge::Value server,
85 const QString &group = {}) {
86 177 Merge::Item item;
87 177 item.id = id;
88
1/1
✓ Branch 6 → 7 taken 177 times.
177 item.name = id.section(QLatin1Char(':'), 0, 0);
89
1/1
✓ Branch 12 → 13 taken 177 times.
177 item.context = id.section(QLatin1Char(':'), 1);
90 177 item.kind = kind;
91 177 item.group = group;
92
2/2
✓ Branch 16 → 17 taken 145 times.
✓ Branch 16 → 18 taken 32 times.
177 item.base = threeWay ? base : Merge::Value{};
93 177 item.local = local;
94 177 item.server = server;
95
2/2
✓ Branch 23 → 24 taken 145 times.
✓ Branch 23 → 26 taken 32 times.
177 if (threeWay) {
96
1/1
✓ Branch 24 → 25 taken 145 times.
145 item.state = Merge::stateOf(item.base, local, server);
97 } else {
98
5/6
✓ Branch 26 → 27 taken 29 times.
✓ Branch 26 → 31 taken 3 times.
✓ Branch 27 → 28 taken 29 times.
✗ Branch 27 → 30 not taken.
✓ Branch 29 → 30 taken 26 times.
✓ Branch 29 → 31 taken 3 times.
32 const bool same = local.present == server.present && (!local.present || local.text == server.text);
99
2/2
✓ Branch 32 → 33 taken 26 times.
✓ Branch 32 → 34 taken 6 times.
32 item.state = same ? Merge::State::Same : Merge::State::Conflict;
100 }
101
1/1
✓ Branch 36 → 37 taken 177 times.
177 plan->items.append(item);
102 177 }
103
104 65 void addMap(const QString &category, const QMap<QString, QString> *base, const QMap<QString, QString> &local,
105 const QMap<QString, QString> &remote) {
106
3/3
✓ Branch 2 → 3 taken 65 times.
✓ Branch 3 → 4 taken 65 times.
✓ Branch 4 → 5 taken 65 times.
65 QStringList keys = local.keys() + remote.keys();
107
2/2
✓ Branch 7 → 8 taken 52 times.
✓ Branch 7 → 12 taken 13 times.
65 if (base)
108
2/2
✓ Branch 8 → 9 taken 52 times.
✓ Branch 9 → 10 taken 52 times.
52 keys += base->keys();
109
1/1
✓ Branch 12 → 13 taken 65 times.
65 keys.removeDuplicates();
110
1/1
✓ Branch 13 → 14 taken 65 times.
65 keys.sort();
111 183 const auto value = [](const QMap<QString, QString> *map, const QString &key) {
112
8/10
✓ Branch 2 → 3 taken 173 times.
✓ Branch 2 → 8 taken 10 times.
✓ Branch 3 → 4 taken 173 times.
✓ Branch 4 → 5 taken 164 times.
✓ Branch 4 → 8 taken 9 times.
✓ Branch 6 → 7 taken 164 times.
✓ Branch 10 → 11 taken 164 times.
✓ Branch 10 → 12 taken 19 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
366 return map && map->contains(key) ? Merge::Value{true, map->value(key)} : Merge::Value{};
113 };
114
4/4
✓ Branch 14 → 15 taken 65 times.
✓ Branch 15 → 16 taken 65 times.
✓ Branch 35 → 17 taken 61 times.
✓ Branch 35 → 36 taken 65 times.
126 for (const QString &key : keys)
115
5/5
✓ Branch 20 → 21 taken 61 times.
✓ Branch 21 → 22 taken 61 times.
✓ Branch 24 → 25 taken 61 times.
✓ Branch 25 → 26 taken 61 times.
✓ Branch 26 → 27 taken 61 times.
183 add(category + QLatin1Char(':') + key, Merge::Kind::Scalar, value(base, key), value(&local, key),
116
1/1
✓ Branch 19 → 20 taken 61 times.
122 value(&remote, key));
117 65 }
118 };
119
120 66 QStringList sharedCategories(const SyncPayload &local, const SyncPayload &remote) {
121 66 QStringList shared;
122
2/2
✓ Branch 15 → 4 taken 211 times.
✓ Branch 15 → 16 taken 66 times.
277 for (const QString &category : local.enabledCategories)
123
3/6
✓ Branch 6 → 7 taken 211 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 211 times.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 211 times.
✗ Branch 11 → 13 not taken.
211 if (remote.enabledCategories.contains(category) && !shared.contains(category))
124
1/1
✓ Branch 12 → 13 taken 211 times.
211 shared.append(category);
125 66 return shared;
126 ✗ }
127
128 92 const SyncPayload &sourceOf(const Merge::Item &item, const SyncPayload &base, const SyncPayload &local,
129 const SyncPayload &remote) {
130
2/3
✓ Branch 2 → 3 taken 28 times.
✗ Branch 2 → 4 not taken.
✓ Branch 2 → 5 taken 64 times.
92 switch (item.choice) {
131 28 case Merge::Choice::Server:
132 28 return remote;
133 ✗ case Merge::Choice::Base:
134 ✗ return base;
135 64 default:
136 64 return local;
137 }
138 }
139
140 } // namespace
141
142 ✗ QString categoryOf(const Merge::Item &item) { return item.name; }
143
144 47 Merge::Plan analyze(const std::optional<SyncPayload> &base, const SyncPayload &local, const SyncPayload &remote) {
145 47 Merge::Plan plan;
146 47 plan.domain = QStringLiteral("settings");
147
3/3
✓ Branch 9 → 10 taken 39 times.
✓ Branch 9 → 12 taken 8 times.
✓ Branch 11 → 13 taken 39 times.
47 plan.baseText = base ? json(*base) : QString();
148
1/1
✓ Branch 15 → 16 taken 47 times.
47 plan.localText = json(local);
149
1/1
✓ Branch 18 → 19 taken 47 times.
47 plan.serverText = json(remote);
150 47 Builder builder{&plan, base.has_value()};
151
1/1
✓ Branch 22 → 23 taken 47 times.
47 const SyncPayload empty;
152
2/2
✓ Branch 24 → 25 taken 39 times.
✓ Branch 24 → 26 taken 8 times.
47 const SyncPayload &b = base ? *base : empty;
153
1/1
✓ Branch 27 → 28 taken 47 times.
47 const QStringList categories = sharedCategories(local, remote);
154
2/2
✓ Branch 34 → 35 taken 30 times.
✓ Branch 34 → 46 taken 17 times.
47 if (categories.contains(QStringLiteral("folderIcons")))
155
3/3
✓ Branch 36 → 37 taken 26 times.
✓ Branch 36 → 38 taken 4 times.
✓ Branch 42 → 43 taken 30 times.
60 builder.addMap(QStringLiteral("folderIcons"), base ? &b.folderIcons : nullptr, local.folderIcons,
156 30 remote.folderIcons);
157
2/2
✓ Branch 52 → 53 taken 12 times.
✓ Branch 52 → 64 taken 35 times.
47 if (categories.contains(QStringLiteral("folderColors")))
158
3/3
✓ Branch 54 → 55 taken 9 times.
✓ Branch 54 → 56 taken 3 times.
✓ Branch 60 → 61 taken 12 times.
24 builder.addMap(QStringLiteral("folderColors"), base ? &b.folderColors : nullptr, local.folderColors,
159 12 remote.folderColors);
160
2/2
✓ Branch 70 → 71 taken 12 times.
✓ Branch 70 → 82 taken 35 times.
47 if (categories.contains(QStringLiteral("calendarColors")))
161
3/3
✓ Branch 72 → 73 taken 9 times.
✓ Branch 72 → 74 taken 3 times.
✓ Branch 78 → 79 taken 12 times.
24 builder.addMap(QStringLiteral("calendarColors"), base ? &b.calendarColors : nullptr, local.calendarColors,
162 12 remote.calendarColors);
163
2/2
✓ Branch 88 → 89 taken 12 times.
✓ Branch 88 → 129 taken 35 times.
47 if (categories.contains(QStringLiteral("hiddenFolders"))) {
164
2/2
✓ Branch 89 → 90 taken 12 times.
✓ Branch 90 → 91 taken 12 times.
12 QStringList paths = local.hiddenFolders + remote.hiddenFolders + b.hiddenFolders;
165
1/1
✓ Branch 92 → 93 taken 12 times.
12 paths.removeDuplicates();
166
4/4
✓ Branch 93 → 94 taken 12 times.
✓ Branch 94 → 95 taken 12 times.
✓ Branch 126 → 96 taken 16 times.
✓ Branch 126 → 127 taken 12 times.
28 for (const QString &path : paths) {
167 56 const auto in = [&path](const QStringList &list) { return Merge::Value{list.contains(path), path}; };
168
2/2
✓ Branch 104 → 105 taken 16 times.
✓ Branch 105 → 106 taken 16 times.
48 builder.add(QStringLiteral("hiddenFolders:") + path, Merge::Kind::SetValue, in(b.hiddenFolders),
169 32 in(local.hiddenFolders), in(remote.hiddenFolders));
170
2/2
✓ Branch 114 → 115 taken 4 times.
✓ Branch 114 → 124 taken 12 times.
16 if (!base) // two-way: a path on one side only is a difference, not "added there"
171
1/1
✓ Branch 120 → 121 taken 4 times.
8 plan.items.last().state = in(local.hiddenFolders).present == in(remote.hiddenFolders).present
172
2/2
✓ Branch 117 → 118 taken 3 times.
✓ Branch 117 → 119 taken 1 time.
4 ? Merge::State::Same
173 : Merge::State::Conflict;
174 }
175 12 }
176
2/2
✓ Branch 135 → 136 taken 14 times.
✓ Branch 135 → 237 taken 33 times.
47 if (categories.contains(QStringLiteral("davAccounts"))) {
177 14 QStringList seen;
178
2/2
✓ Branch 234 → 138 taken 19 times.
✓ Branch 234 → 235 taken 14 times.
33 for (const SyncPayload::CardDavAccount &remoteAccount : remote.carddavAccounts) {
179 19 const QString id = remoteAccount.id;
180
2/2
✓ Branch 141 → 142 taken 1 time.
✓ Branch 141 → 143 taken 18 times.
19 if (seen.contains(id))
181 1 continue; // a repeated id: the first one counts, as in accountOf()
182
1/1
✓ Branch 143 → 144 taken 18 times.
18 seen.append(id);
183
1/1
✓ Branch 144 → 145 taken 18 times.
18 const QString keyId = keyOfAccount(id);
184
1/1
✓ Branch 145 → 146 taken 18 times.
18 const SyncPayload::CardDavAccount *localAccount = accountOf(local, id);
185
1/1
✓ Branch 146 → 147 taken 18 times.
18 const SyncPayload::CardDavAccount *baseAccount = accountOf(b, id);
186
2/2
✓ Branch 147 → 148 taken 6 times.
✓ Branch 147 → 171 taken 12 times.
18 if (!localAccount) {
187
2/2
✓ Branch 154 → 155 taken 6 times.
✓ Branch 155 → 156 taken 6 times.
18 builder.add(accountItemId(id), Merge::Kind::Component,
188
1/3
✗ Branch 150 → 151 not taken.
✓ Branch 150 → 152 taken 6 times.
✗ Branch 151 → 153 not taken.
12 baseAccount ? Merge::Value{true, accountText(*baseAccount)} : Merge::Value{}, {},
189 {true, accountText(remoteAccount)});
190
4/6
✓ Branch 163 → 164 taken 4 times.
✓ Branch 163 → 165 taken 2 times.
✓ Branch 164 → 165 taken 4 times.
✗ Branch 164 → 166 not taken.
✓ Branch 167 → 168 taken 6 times.
✗ Branch 167 → 170 not taken.
6 if (!base || !baseAccount) // new on the server: offered, never a forced conflict
191
1/1
✓ Branch 168 → 169 taken 6 times.
6 plan.items.last().state = Merge::State::ServerOnly;
192 6 continue;
193 }
194 // Identity changes are never adopted (applyRemote): only selections.
195
5/6
✓ Branch 172 → 173 taken 11 times.
✓ Branch 172 → 175 taken 1 time.
✗ Branch 174 → 175 not taken.
✓ Branch 174 → 176 taken 11 times.
✓ Branch 177 → 178 taken 1 time.
✓ Branch 177 → 179 taken 11 times.
12 if (localAccount->serverUrl != remoteAccount.serverUrl || localAccount->username != remoteAccount.username)
196 1 continue;
197
1/1
✓ Branch 182 → 183 taken 11 times.
11 const QString group = QStringLiteral("dav:") + keyId;
198
2/2
✓ Branch 193 → 194 taken 11 times.
✓ Branch 194 → 195 taken 11 times.
22 builder.add(accountItemId(id, QStringLiteral("books")), Merge::Kind::Scalar,
199
3/3
✓ Branch 187 → 188 taken 9 times.
✓ Branch 187 → 189 taken 2 times.
✓ Branch 188 → 190 taken 9 times.
22 baseAccount ? Merge::Value{true, listText(baseAccount->selectedBooks)} : Merge::Value{},
200 22 {true, listText(localAccount->selectedBooks)}, {true, listText(remoteAccount.selectedBooks)}, group);
201
1/1
✓ Branch 201 → 202 taken 11 times.
11 const SyncPayload::CalDavSyncConfig *baseConfig = configOf(b, id);
202
1/1
✓ Branch 202 → 203 taken 11 times.
11 const SyncPayload::CalDavSyncConfig *localConfig = configOf(local, id);
203
1/1
✓ Branch 203 → 204 taken 11 times.
11 const SyncPayload::CalDavSyncConfig *remoteConfig = configOf(remote, id);
204
1/4
✗ Branch 204 → 205 not taken.
✓ Branch 204 → 206 taken 11 times.
✗ Branch 205 → 206 not taken.
✗ Branch 205 → 221 not taken.
11 if (localConfig || remoteConfig)
205
2/2
✓ Branch 212 → 213 taken 11 times.
✓ Branch 213 → 214 taken 11 times.
11 builder.add(accountItemId(id, QStringLiteral("calendars")), Merge::Kind::Scalar,
206 22 {baseConfig != nullptr, configText(baseConfig)}, {localConfig != nullptr, configText(localConfig)},
207 11 {remoteConfig != nullptr, configText(remoteConfig)}, group);
208 26 }
209 14 }
210 // Sprint 108: addresses both installations have (applyRemote never adds
211 // one), and every template (added, changed or removed).
212
2/2
✓ Branch 243 → 244 taken 11 times.
✓ Branch 243 → 290 taken 36 times.
47 if (categories.contains(QStringLiteral("identities"))) {
213
2/2
✓ Branch 244 → 245 taken 11 times.
✓ Branch 245 → 246 taken 11 times.
11 const QMap<QString, QString> l = textMap(local.identities), r = textMap(remote.identities),
214
1/1
✓ Branch 246 → 247 taken 11 times.
11 bt = textMap(b.identities);
215
4/4
✓ Branch 247 → 248 taken 11 times.
✓ Branch 283 → 284 taken 13 times.
✓ Branch 285 → 249 taken 2 times.
✓ Branch 285 → 286 taken 11 times.
13 for (auto it = l.constBegin(); it != l.constEnd(); ++it)
216
2/3
✓ Branch 250 → 251 taken 2 times.
✓ Branch 251 → 252 taken 2 times.
✗ Branch 251 → 282 not taken.
2 if (r.contains(it.key()))
217
2/2
✓ Branch 269 → 270 taken 2 times.
✓ Branch 270 → 271 taken 2 times.
10 builder.add(QStringLiteral("identities:") + it.key(), Merge::Kind::Scalar,
218
4/8
✓ Branch 259 → 260 taken 2 times.
✓ Branch 260 → 261 taken 2 times.
✗ Branch 260 → 264 not taken.
✓ Branch 263 → 265 taken 2 times.
✓ Branch 275 → 276 taken 2 times.
✗ Branch 275 → 277 not taken.
✗ Branch 493 → 494 not taken.
✗ Branch 493 → 495 not taken.
4 bt.contains(it.key()) ? Merge::Value{true, bt.value(it.key())} : Merge::Value{},
219 2 {true, it.value()}, {true, r.value(it.key())});
220 11 }
221
2/2
✓ Branch 296 → 297 taken 11 times.
✓ Branch 296 → 314 taken 36 times.
47 if (categories.contains(QStringLiteral("templates"))) {
222
1/1
✓ Branch 297 → 298 taken 11 times.
11 const QMap<QString, QString> bt = textMap(b.templates);
223
5/5
✓ Branch 298 → 299 taken 11 times.
✓ Branch 299 → 300 taken 11 times.
✓ Branch 301 → 302 taken 8 times.
✓ Branch 301 → 303 taken 3 times.
✓ Branch 307 → 308 taken 11 times.
22 builder.addMap(QStringLiteral("templates"), base ? &bt : nullptr, textMap(local.templates), textMap(remote.templates));
224 11 }
225
2/2
✓ Branch 320 → 321 taken 35 times.
✓ Branch 320 → 350 taken 12 times.
47 if (categories.contains(QStringLiteral("general"))) {
226
1/1
✓ Branch 328 → 329 taken 35 times.
70 builder.add(QStringLiteral("general:defaultView"), Merge::Kind::Scalar, {true, b.general.defaultView},
227 70 {true, local.general.defaultView}, {true, remote.general.defaultView});
228
1/1
✓ Branch 342 → 343 taken 35 times.
70 builder.add(QStringLiteral("general:language"), Merge::Kind::Scalar, {true, b.general.language},
229 70 {true, local.general.language}, {true, remote.general.language});
230 }
231
1/1
✓ Branch 350 → 351 taken 47 times.
47 Merge::applyDefaults(&plan);
232 47 return plan;
233 147 }
234
235 19 SyncPayload apply(const Merge::Plan &plan) {
236
1/1
✓ Branch 2 → 3 taken 19 times.
19 const SyncPayload local = payloadOf(plan.localText);
237
1/1
✓ Branch 3 → 4 taken 19 times.
19 const SyncPayload remote = payloadOf(plan.serverText);
238
9/31
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 17 taken 15 times.
✓ Branch 17 → 18 taken 15 times.
✓ Branch 18 → 19 taken 4 times.
✓ Branch 18 → 20 taken 15 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 22 taken 15 times.
✓ Branch 22 → 23 taken 4 times.
✓ Branch 22 → 24 taken 15 times.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 249 not taken.
✗ Branch 247 → 248 not taken.
✗ Branch 247 → 249 not taken.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 252 not taken.
✗ Branch 254 → 255 not taken.
✗ Branch 254 → 257 not taken.
✗ Branch 255 → 256 not taken.
✗ Branch 255 → 257 not taken.
✗ Branch 258 → 259 not taken.
✗ Branch 258 → 260 not taken.
✗ Branch 262 → 263 not taken.
✗ Branch 262 → 265 not taken.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 265 not taken.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 273 not taken.
✗ Branch 271 → 272 not taken.
✗ Branch 271 → 273 not taken.
31 const SyncPayload base = plan.baseText.isEmpty() ? SyncPayload{} : payloadOf(plan.baseText);
239 19 SyncPayload result = local;
240
1/1
✓ Branch 25 → 26 taken 19 times.
19 result.enabledCategories = sharedCategories(local, remote);
241 19 QStringList hidden;
242
2/2
✓ Branch 195 → 30 taken 92 times.
✓ Branch 195 → 196 taken 19 times.
111 for (const Merge::Item &item : plan.items) {
243 92 const SyncPayload &source = sourceOf(item, base, local, remote);
244 92 const QString key = item.context;
245 92 const QString category = item.name;
246
5/6
✓ Branch 36 → 37 taken 72 times.
✓ Branch 36 → 43 taken 20 times.
✓ Branch 39 → 40 taken 64 times.
✓ Branch 39 → 43 taken 8 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 64 times.
156 if (category == QLatin1String("folderIcons") || category == QLatin1String("folderColors") ||
247
2/2
✓ Branch 45 → 46 taken 28 times.
✓ Branch 45 → 75 taken 64 times.
156 category == QLatin1String("calendarColors")) {
248 28 QMap<QString, QString> &target = category == QLatin1String("folderIcons") ? result.folderIcons
249 8 : category == QLatin1String("folderColors") ? result.folderColors
250
3/4
✓ Branch 48 → 49 taken 20 times.
✓ Branch 48 → 50 taken 8 times.
✓ Branch 52 → 53 taken 8 times.
✗ Branch 52 → 54 not taken.
36 : result.calendarColors;
251 28 const QMap<QString, QString> &from = category == QLatin1String("folderIcons") ? source.folderIcons
252 8 : category == QLatin1String("folderColors") ? source.folderColors
253
3/4
✓ Branch 58 → 59 taken 20 times.
✓ Branch 58 → 60 taken 8 times.
✓ Branch 62 → 63 taken 8 times.
✗ Branch 62 → 64 not taken.
36 : source.calendarColors;
254
3/3
✓ Branch 66 → 67 taken 28 times.
✓ Branch 67 → 68 taken 27 times.
✓ Branch 67 → 74 taken 1 time.
28 if (from.contains(key))
255
2/2
✓ Branch 69 → 70 taken 27 times.
✓ Branch 70 → 71 taken 27 times.
27 target.insert(key, from.value(key));
256 else
257
1/1
✓ Branch 74 → 183 taken 1 time.
1 target.remove(key);
258
2/2
✓ Branch 77 → 78 taken 8 times.
✓ Branch 77 → 81 taken 56 times.
64 } else if (category == QLatin1String("hiddenFolders")) {
259
2/2
✓ Branch 79 → 80 taken 7 times.
✓ Branch 79 → 183 taken 1 time.
8 if (source.hiddenFolders.contains(key))
260
1/1
✓ Branch 80 → 183 taken 7 times.
7 hidden.append(key);
261
2/2
✓ Branch 83 → 84 taken 22 times.
✓ Branch 83 → 144 taken 34 times.
56 } else if (category == QLatin1String("davAccounts")) {
262 // "<id>" (the whole account) or "<id>:books" / "<id>:calendars"; the
263 // id is escaped (keyOfAccount), so the first ':' ends it.
264
2/2
✓ Branch 87 → 88 taken 22 times.
✓ Branch 88 → 89 taken 22 times.
22 const QString id = accountOfKey(key.section(QLatin1Char(':'), 0, 0));
265
1/1
✓ Branch 93 → 94 taken 22 times.
22 const QString part = key.section(QLatin1Char(':'), 1);
266
2/2
✓ Branch 95 → 96 taken 8 times.
✓ Branch 95 → 109 taken 14 times.
22 if (part.isEmpty()) {
267
1/1
✓ Branch 96 → 97 taken 8 times.
8 const SyncPayload::CardDavAccount *offered = accountOf(remote, id);
268
7/9
✓ Branch 97 → 98 taken 6 times.
✓ Branch 97 → 102 taken 2 times.
✓ Branch 98 → 99 taken 6 times.
✗ Branch 98 → 102 not taken.
✓ Branch 99 → 100 taken 6 times.
✓ Branch 100 → 101 taken 6 times.
✗ Branch 100 → 102 not taken.
✓ Branch 103 → 104 taken 6 times.
✓ Branch 103 → 108 taken 2 times.
8 if (item.choice == Merge::Choice::Server && offered && !accountOf(result, id)) {
269
1/1
✓ Branch 104 → 105 taken 6 times.
6 result.carddavAccounts.append(*offered);
270
3/3
✓ Branch 105 → 106 taken 6 times.
✓ Branch 106 → 107 taken 3 times.
✓ Branch 106 → 108 taken 3 times.
6 if (const auto *config = configOf(remote, id))
271
1/1
✓ Branch 107 → 108 taken 3 times.
3 result.caldavConfigs.append(*config);
272 }
273 8 continue;
274 8 }
275
1/1
✓ Branch 109 → 110 taken 14 times.
14 const auto *sourceAccount = accountOf(source, id);
276
4/4
✓ Branch 110 → 111 taken 14 times.
✓ Branch 111 → 112 taken 14 times.
✓ Branch 126 → 113 taken 14 times.
✓ Branch 126 → 127 taken 14 times.
28 for (SyncPayload::CardDavAccount &account : result.carddavAccounts)
277
6/8
✓ Branch 115 → 116 taken 14 times.
✗ Branch 115 → 121 not taken.
✓ Branch 118 → 119 taken 7 times.
✓ Branch 118 → 121 taken 7 times.
✓ Branch 119 → 120 taken 7 times.
✗ Branch 119 → 121 not taken.
✓ Branch 122 → 123 taken 7 times.
✓ Branch 122 → 124 taken 7 times.
14 if (account.id == id && part == QLatin1String("books") && sourceAccount)
278 7 account.selectedBooks = sourceAccount->selectedBooks;
279
2/2
✓ Branch 129 → 130 taken 7 times.
✓ Branch 129 → 134 taken 7 times.
14 if (part == QLatin1String("calendars")) {
280
1/1
✓ Branch 130 → 131 taken 7 times.
7 result.caldavConfigs.removeIf(
281 7 [&id](const SyncPayload::CalDavSyncConfig &config) { return config.carddavAccountId == id; });
282
2/3
✓ Branch 131 → 132 taken 7 times.
✓ Branch 132 → 133 taken 7 times.
✗ Branch 132 → 134 not taken.
7 if (const auto *config = configOf(source, id))
283
1/1
✓ Branch 133 → 134 taken 7 times.
7 result.caldavConfigs.append(*config);
284 }
285
10/10
✓ Branch 136 → 137 taken 14 times.
✓ Branch 136 → 138 taken 8 times.
✓ Branch 140 → 141 taken 14 times.
✓ Branch 140 → 143 taken 8 times.
✓ Branch 146 → 147 taken 33 times.
✓ Branch 146 → 150 taken 1 time.
✓ Branch 149 → 150 taken 3 times.
✓ Branch 149 → 151 taken 30 times.
✓ Branch 152 → 153 taken 4 times.
✓ Branch 152 → 172 taken 30 times.
64 } else if (category == QLatin1String("identities") || category == QLatin1String("templates")) {
286
2/2
✓ Branch 155 → 156 taken 1 time.
✓ Branch 155 → 157 taken 3 times.
4 QMap<QString, QJsonObject> &target = category == QLatin1String("identities") ? result.identities : result.templates;
287
2/2
✓ Branch 160 → 161 taken 1 time.
✓ Branch 160 → 162 taken 3 times.
4 const QMap<QString, QJsonObject> &from = category == QLatin1String("identities") ? source.identities : source.templates;
288
3/3
✓ Branch 163 → 164 taken 4 times.
✓ Branch 164 → 165 taken 3 times.
✓ Branch 164 → 171 taken 1 time.
4 if (from.contains(key))
289
3/3
✓ Branch 165 → 166 taken 3 times.
✓ Branch 166 → 167 taken 3 times.
✓ Branch 167 → 168 taken 3 times.
3 target.insert(key, from.value(key));
290 else
291
1/1
✓ Branch 171 → 183 taken 1 time.
1 target.remove(key);
292
1/2
✓ Branch 174 → 175 taken 30 times.
✗ Branch 174 → 183 not taken.
30 } else if (category == QLatin1String("general")) {
293
2/2
✓ Branch 177 → 178 taken 15 times.
✓ Branch 177 → 179 taken 15 times.
30 if (key == QLatin1String("defaultView"))
294 15 result.general.defaultView = source.general.defaultView;
295
1/2
✓ Branch 181 → 182 taken 15 times.
✗ Branch 181 → 183 not taken.
15 else if (key == QLatin1String("language"))
296 15 result.general.language = source.general.language;
297 }
298 100 }
299 // Hidden folders: the local order, then additions in the server's order.
300 19 QStringList folders;
301
2/2
✓ Branch 213 → 198 taken 57 times.
✓ Branch 213 → 214 taken 19 times.
76 for (const QStringList *list : {&local.hiddenFolders, &remote.hiddenFolders, &base.hiddenFolders})
302
2/2
✓ Branch 211 → 200 taken 24 times.
✓ Branch 211 → 212 taken 57 times.
81 for (const QString &path : *list)
303
6/6
✓ Branch 202 → 203 taken 19 times.
✓ Branch 202 → 206 taken 5 times.
✓ Branch 204 → 205 taken 7 times.
✓ Branch 204 → 206 taken 12 times.
✓ Branch 207 → 208 taken 7 times.
✓ Branch 207 → 209 taken 17 times.
24 if (hidden.contains(path) && !folders.contains(path))
304
1/1
✓ Branch 208 → 209 taken 7 times.
7 folders.append(path);
305
2/2
✓ Branch 220 → 221 taken 7 times.
✓ Branch 220 → 222 taken 12 times.
38 if (result.enabledCategories.contains(QStringLiteral("hiddenFolders")))
306 7 result.hiddenFolders = folders;
307 // Parsing keeps MaxComposeEntries per map; the merge of three payloads
308 // could hold more, which a round trip of the result would cut.
309
2/2
✓ Branch 238 → 224 taken 38 times.
✓ Branch 238 → 239 taken 19 times.
57 for (QMap<QString, QJsonObject> *map : {&result.identities, &result.templates})
310
2/5
✗ Branch 225 → 226 not taken.
✗ Branch 233 → 234 not taken.
✓ Branch 235 → 236 taken 38 times.
✗ Branch 236 → 225 not taken.
✓ Branch 236 → 237 taken 38 times.
38 while (map->size() > SyncPayload::MaxComposeEntries) map->erase(std::prev(map->end()));
311 19 return result;
312 19 }
313
314 13 QStringList conflictCategories(const Merge::Plan &plan) {
315 13 QStringList categories;
316
2/2
✓ Branch 14 → 4 taken 57 times.
✓ Branch 14 → 15 taken 13 times.
70 for (const Merge::Item &item : plan.items)
317
5/6
✓ Branch 5 → 6 taken 12 times.
✓ Branch 5 → 9 taken 45 times.
✓ Branch 7 → 8 taken 12 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 12 taken 45 times.
57 if (item.state == Merge::State::Conflict && !categories.contains(item.name))
318
1/1
✓ Branch 11 → 12 taken 12 times.
12 categories.append(item.name);
319 13 return categories;
320 ✗ }
321
322 30 QString accountItemId(const QString &accountId, const QString &part) {
323
2/2
✓ Branch 2 → 3 taken 30 times.
✓ Branch 6 → 7 taken 30 times.
60 const QString id = QStringLiteral("davAccounts:") + keyOfAccount(accountId);
324
6/8
✓ Branch 11 → 12 taken 7 times.
✓ Branch 11 → 13 taken 23 times.
✓ Branch 15 → 16 taken 23 times.
✓ Branch 16 → 17 taken 23 times.
✓ Branch 18 → 19 taken 23 times.
✓ Branch 18 → 20 taken 7 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
60 return part.isEmpty() ? id : id + QLatin1Char(':') + part;
325 30 }
326
327 } // namespace SettingsMergePlan
328