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.2% 431 / 0 / 448
Functions: 100.0% 20 / 0 / 20
Branches: 79.5% 679 / 0 / 854

data/SettingsCollector.cpp
Line Branch Exec Source
1 #include "data/SettingsCollector.h"
2 #include "data/DavSelectionList.h"
3
4 #include <QDir>
5 #include <QFile>
6 #include <QHash>
7 #include <QLoggingCategory>
8 #include <QRegularExpression>
9 #include <QSet>
10 #include <QSettings>
11 #include <QVariantMap>
12
13 #include "data/CalendarStore.h"
14 #include "data/ComposeSettingsSync.h"
15 #include "data/IdentityStore.h"
16 #include "data/TemplateStore.h"
17 #include "data/DavCredentials.h"
18 #include "data/MailCache.h"
19 #include "data/FolderPreferences.h"
20
21
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 46 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
49 Q_LOGGING_CATEGORY(lcSettingsSync, "mailjd.settingssync")
22
23 namespace {
24
25 constexpr int kMaxDavAccounts = 16;
26 constexpr int kMaxDavSelections = 256;
27 constexpr int kMaxSyncedMapEntries = 4096;
28
29 8 const QRegularExpression &iconNamePattern() {
30 static const QRegularExpression pattern(
31
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 15 taken 5 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
11 QStringLiteral("^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$"));
32 8 return pattern;
33 }
34
35 6 const QRegularExpression &colorPattern() {
36 static const QRegularExpression pattern(
37 2 QStringLiteral("^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|"
38
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 15 taken 5 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
8 "[0-9A-Fa-f]{8})$"));
39 6 return pattern;
40 }
41
42 229 QString normalizedCategory(const QString &category) {
43 229 return SyncPayload::normalizedCategory(category);
44 }
45
46 16 QStringList effectiveRemoteCategories(const SyncPayload &payload,
47 const QSettings &settings) {
48 const QStringList configured =
49 settings
50
1/1
✓ Branch 8 → 9 taken 16 times.
48 .value(QStringLiteral("sync/categories"),
51
1/1
✓ Branch 2 → 3 taken 16 times.
32 SyncPayload::allCategories())
52
1/1
✓ Branch 9 → 10 taken 16 times.
16 .toStringList();
53 16 QSet<QString> localPolicy;
54
2/2
✓ Branch 24 → 18 taken 70 times.
✓ Branch 24 → 25 taken 16 times.
86 for (const QString &category : configured)
55
2/2
✓ Branch 19 → 20 taken 70 times.
✓ Branch 20 → 21 taken 70 times.
70 localPolicy.insert(normalizedCategory(category));
56
57 // Privacy policy and the external-content whitelist are intentionally not
58 // remotely applicable. The IMAP message is only a transport container and
59 // does not establish the sender as an authorized device.
60 const QSet<QString> remotelyApplicable = {
61 16 QStringLiteral("folderIcons"), QStringLiteral("folderColors"),
62 16 QStringLiteral("calendarColors"), QStringLiteral("hiddenFolders"),
63 16 QStringLiteral("davAccounts"), QStringLiteral("general"),
64
2/4
✓ Branch 51 → 52 taken 128 times.
✓ Branch 51 → 53 taken 16 times.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
144 QStringLiteral("identities"), QStringLiteral("templates")};
65
66 16 QStringList result;
67
2/2
✓ Branch 78 → 63 taken 37 times.
✓ Branch 78 → 79 taken 16 times.
53 for (const QString &rawCategory : payload.enabledCategories) {
68
1/1
✓ Branch 64 → 65 taken 37 times.
37 const QString category = normalizedCategory(rawCategory);
69
1/2
✓ Branch 68 → 69 taken 33 times.
✗ Branch 68 → 72 not taken.
70 if (localPolicy.contains(category) &&
70
4/4
✓ Branch 66 → 67 taken 33 times.
✓ Branch 66 → 72 taken 4 times.
✓ Branch 73 → 74 taken 32 times.
✓ Branch 73 → 75 taken 5 times.
70 remotelyApplicable.contains(category) &&
71
2/2
✓ Branch 70 → 71 taken 32 times.
✓ Branch 70 → 72 taken 1 time.
33 !result.contains(category)) {
72
1/1
✓ Branch 74 → 75 taken 32 times.
32 result.append(category);
73 }
74 37 }
75 16 return result;
76 160 }
77
78 43 bool safeText(const QString &value, qsizetype maxLength) {
79
5/6
✓ Branch 3 → 4 taken 41 times.
✓ Branch 3 → 6 taken 2 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 41 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 41 times.
43 if (value.isEmpty() || value.size() > maxLength)
80 2 return false;
81
2/2
✓ Branch 26 → 12 taken 233 times.
✓ Branch 26 → 27 taken 40 times.
273 for (const QChar ch : value) {
82
3/4
✓ Branch 14 → 15 taken 233 times.
✗ Branch 14 → 21 not taken.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 22 taken 232 times.
234 if (ch == QChar::Null || (ch.category() == QChar::Other_Control &&
83
3/4
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 232 times.
234 ch != QLatin1Char('\t')))
84 1 return false;
85 }
86 40 return true;
87 }
88
89 9 QStringList safeDavSelections(const QStringList &values) {
90 9 QStringList result;
91
1/1
✓ Branch 4 → 5 taken 9 times.
9 result.reserve(qMin(values.size(), qsizetype{kMaxDavSelections}));
92
2/2
✓ Branch 21 → 7 taken 8 times.
✓ Branch 21 → 22 taken 9 times.
17 for (const QString &value : values) {
93
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 8 times.
8 if (result.size() >= kMaxDavSelections)
94 ✗ break;
95
6/6
✓ Branch 12 → 13 taken 7 times.
✓ Branch 12 → 16 taken 1 time.
✓ Branch 14 → 15 taken 6 times.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 17 → 18 taken 6 times.
✓ Branch 17 → 19 taken 2 times.
8 if (safeText(value, 4096) && !result.contains(value))
96
1/1
✓ Branch 18 → 19 taken 6 times.
6 result.append(value);
97 }
98 9 return result;
99 ✗ }
100
101 8 bool validDavAccount(const SyncPayload::CardDavAccount &account) {
102
1/4
✓ Branch 7 → 8 taken 8 times.
✗ Branch 7 → 12 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
16 return safeText(account.id.trimmed(), 128) &&
103
4/8
✓ Branch 2 → 3 taken 8 times.
✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 12 not taken.
✓ Branch 5 → 6 taken 8 times.
✓ Branch 17 → 18 taken 8 times.
✗ Branch 17 → 19 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
24 safeText(account.username.trimmed(), 1024) &&
104
5/9
✓ Branch 8 → 9 taken 8 times.
✓ Branch 10 → 11 taken 7 times.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 8 times.
✗ Branch 13 → 15 not taken.
✓ Branch 15 → 16 taken 8 times.
✗ Branch 15 → 17 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
24 !DavCredentials::canonicalOrigin(account.serverUrl).isEmpty();
105 }
106
107 4 bool sameDavIdentity(const QVariantMap &local,
108 const SyncPayload::CardDavAccount &remote) {
109 // The caller resolves `local` through a hash keyed by remote.id before
110 // invoking this helper. Rechecking that same key was a permanently-true
111 // arm; identity protection here therefore covers the two values a remote
112 // payload must never be allowed to change.
113
8/25
✓ Branch 7 → 8 taken 4 times.
✓ Branch 8 → 9 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 40 → 41 taken 4 times.
✗ Branch 40 → 42 not taken.
✓ Branch 42 → 43 taken 4 times.
✗ Branch 42 → 44 not taken.
✓ Branch 44 → 45 taken 4 times.
✗ Branch 44 → 46 not taken.
✓ Branch 46 → 47 taken 4 times.
✗ Branch 46 → 48 not taken.
✓ Branch 48 → 49 taken 4 times.
✗ Branch 48 → 50 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
12 return local.value(QStringLiteral("username")).toString().trimmed() ==
114
5/9
✓ Branch 2 → 3 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✗ Branch 11 → 23 not taken.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 50 → 51 taken 4 times.
✗ Branch 50 → 52 not taken.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
16 remote.username.trimmed() &&
115
1/3
✓ Branch 19 → 20 taken 4 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
8 DavCredentials::canonicalOrigin(
116
7/22
✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 28 not taken.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 4 times.
✗ Branch 30 → 32 not taken.
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 34 not taken.
✓ Branch 34 → 35 taken 4 times.
✗ Branch 34 → 36 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
16 local.value(QStringLiteral("serverUrl")).toString()) ==
117
4/9
✓ Branch 12 → 13 taken 4 times.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 26 not taken.
✓ Branch 36 → 37 taken 4 times.
✗ Branch 36 → 38 not taken.
✓ Branch 38 → 39 taken 4 times.
✗ Branch 38 → 40 not taken.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
16 DavCredentials::canonicalOrigin(remote.serverUrl);
118 }
119
120 10 QList<QVariantMap> readSettingsArray(QSettings &settings,
121 const QString &name) {
122 10 QList<QVariantMap> result;
123
1/1
✓ Branch 3 → 4 taken 10 times.
10 const int count = settings.beginReadArray(name);
124
1/1
✓ Branch 4 → 5 taken 10 times.
10 result.reserve(count);
125
2/2
✓ Branch 23 → 6 taken 6 times.
✓ Branch 23 → 24 taken 10 times.
16 for (int i = 0; i < count; ++i) {
126
1/1
✓ Branch 6 → 7 taken 6 times.
6 settings.setArrayIndex(i);
127 6 QVariantMap fields;
128
5/5
✓ Branch 7 → 8 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 18 → 11 taken 21 times.
✓ Branch 18 → 19 taken 6 times.
27 for (const QString &key : settings.childKeys())
129
2/2
✓ Branch 13 → 14 taken 21 times.
✓ Branch 14 → 15 taken 21 times.
27 fields.insert(key, settings.value(key));
130
1/1
✓ Branch 20 → 21 taken 6 times.
6 result.append(fields);
131 6 }
132
1/1
✓ Branch 24 → 25 taken 10 times.
10 settings.endArray();
133 10 return result;
134 ✗ }
135
136 5 void writeSettingsArray(QSettings &settings, const QString &name,
137 const QList<QVariantMap> &items) {
138
1/1
✓ Branch 3 → 4 taken 5 times.
5 settings.remove(name);
139
1/1
✓ Branch 6 → 7 taken 5 times.
5 settings.beginWriteArray(name, items.size());
140
2/2
✓ Branch 22 → 8 taken 8 times.
✓ Branch 22 → 23 taken 5 times.
13 for (int i = 0; i < items.size(); ++i) {
141 8 settings.setArrayIndex(i);
142 8 const QVariantMap &fields = items[i];
143
4/4
✓ Branch 10 → 11 taken 8 times.
✓ Branch 17 → 18 taken 43 times.
✓ Branch 19 → 12 taken 35 times.
✓ Branch 19 → 20 taken 8 times.
43 for (auto it = fields.cbegin(); it != fields.cend(); ++it)
144
1/1
✓ Branch 15 → 16 taken 35 times.
35 settings.setValue(it.key(), it.value());
145 }
146 5 settings.endArray();
147 5 }
148
149 } // namespace
150
151 6 QString SettingsCollector::calendarColorKey(const QString &accountId,
152 const QString &calendarPath) {
153 12 const auto encode = [](const QString &part) {
154 return QString::fromLatin1(
155
2/2
✓ Branch 2 → 3 taken 12 times.
✓ Branch 4 → 5 taken 12 times.
24 part.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
156
1/1
✓ Branch 5 → 6 taken 12 times.
24 QByteArray::OmitTrailingEquals));
157 };
158
3/3
✓ Branch 5 → 6 taken 6 times.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 7 → 8 taken 6 times.
6 return QStringLiteral("v2:%1:%2").arg(encode(accountId), encode(calendarPath));
159 }
160
161 3 static bool decodeCalendarColorKey(const QString &key, QString *accountId,
162 QString *calendarPath) {
163
3/3
✓ Branch 5 → 6 taken 3 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 1 time.
3 if (!key.startsWith(QStringLiteral("v2:")))
164 2 return false;
165 1 const qsizetype separator = key.indexOf(QLatin1Char(':'), 3);
166
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 1 time.
1 if (separator < 0)
167 ✗ return false;
168 2 const auto decode = [](QStringView part) {
169
1/1
✓ Branch 4 → 5 taken 2 times.
6 return QString::fromUtf8(QByteArray::fromBase64(
170
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 5 → 6 taken 2 times.
6 part.toLatin1(), QByteArray::Base64UrlEncoding));
171 };
172
1/1
✓ Branch 17 → 18 taken 1 time.
1 *accountId = decode(QStringView{key}.mid(3, separator - 3));
173
1/1
✓ Branch 22 → 23 taken 1 time.
1 *calendarPath = decode(QStringView{key}.mid(separator + 1));
174 1 return !calendarPath->isEmpty();
175 }
176
177 // ═══════════════════════════════════════════════════════
178 // collectLocal – gather all local settings into SyncPayload
179 // ═══════════════════════════════════════════════════════
180
181 63 SyncPayload SettingsCollector::collectLocal(MailCache *cache,
182 const QString &configDir,
183 CalendarStore *calStore) {
184
1/1
✓ Branch 2 → 3 taken 63 times.
63 SyncPayload p;
185 63 p.version = 1;
186
1/1
✓ Branch 3 → 4 taken 63 times.
63 p.lastModified = QDateTime::currentDateTimeUtc();
187
188
1/1
✓ Branch 6 → 7 taken 63 times.
63 QSettings s;
189
190 // --- enabledCategories ---
191 const QStringList configuredCategories =
192
2/2
✓ Branch 7 → 8 taken 63 times.
✓ Branch 13 → 14 taken 63 times.
189 s.value(QStringLiteral("sync/categories"), SyncPayload::allCategories())
193
1/1
✓ Branch 14 → 15 taken 63 times.
63 .toStringList();
194
2/2
✓ Branch 38 → 22 taken 122 times.
✓ Branch 38 → 39 taken 63 times.
185 for (const QString &rawCategory : configuredCategories) {
195
1/1
✓ Branch 23 → 24 taken 122 times.
122 const QString category = normalizedCategory(rawCategory);
196
4/9
✓ Branch 24 → 25 taken 122 times.
✓ Branch 26 → 27 taken 122 times.
✗ Branch 26 → 30 not taken.
✓ Branch 31 → 32 taken 122 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 122 times.
✗ Branch 33 → 35 not taken.
✗ Branch 480 → 481 not taken.
✗ Branch 480 → 482 not taken.
244 if (SyncPayload::allCategories().contains(category) &&
197
1/2
✓ Branch 28 → 29 taken 122 times.
✗ Branch 28 → 30 not taken.
122 !p.enabledCategories.contains(category)) {
198
1/1
✓ Branch 34 → 35 taken 122 times.
122 p.enabledCategories.append(category);
199 }
200 122 }
201
202 // clientId
203
2/2
✓ Branch 43 → 44 taken 63 times.
✓ Branch 44 → 45 taken 63 times.
63 p.clientId = s.value(QStringLiteral("sync/clientId")).toString();
204
205 // --- folderIcons ---
206 // Use allKeys() instead of childKeys() because QSettings treats '/' as
207 // a group separator. Folder paths with '/' delimiter (e.g. "Work/Projects")
208 // become nested subgroups that childKeys() would miss.
209
2/2
✓ Branch 56 → 57 taken 37 times.
✓ Branch 56 → 81 taken 26 times.
126 if (p.enabledCategories.contains(QStringLiteral("folderIcons"))) {
210
1/1
✓ Branch 61 → 62 taken 37 times.
37 s.beginGroup(QStringLiteral("folder/icon"));
211
1/1
✓ Branch 64 → 65 taken 37 times.
37 const auto iconKeys = s.allKeys();
212
2/2
✓ Branch 77 → 67 taken 34 times.
✓ Branch 77 → 78 taken 37 times.
71 for (const auto &key : iconKeys) {
213
3/3
✓ Branch 69 → 70 taken 34 times.
✓ Branch 70 → 71 taken 34 times.
✓ Branch 71 → 72 taken 34 times.
34 p.folderIcons[key] = s.value(key).toString();
214 }
215
1/1
✓ Branch 78 → 79 taken 37 times.
37 s.endGroup();
216 37 }
217
218 // --- folderColors ---
219
2/2
✓ Branch 87 → 88 taken 4 times.
✓ Branch 87 → 112 taken 59 times.
126 if (p.enabledCategories.contains(QStringLiteral("folderColors"))) {
220
1/1
✓ Branch 92 → 93 taken 4 times.
4 s.beginGroup(QStringLiteral("folder/color"));
221
1/1
✓ Branch 95 → 96 taken 4 times.
4 const auto colorKeys = s.allKeys();
222
2/2
✓ Branch 108 → 98 taken 1 time.
✓ Branch 108 → 109 taken 4 times.
5 for (const auto &key : colorKeys) {
223
3/3
✓ Branch 100 → 101 taken 1 time.
✓ Branch 101 → 102 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
1 p.folderColors[key] = s.value(key).toString();
224 }
225
1/1
✓ Branch 109 → 110 taken 4 times.
4 s.endGroup();
226 4 }
227
228 // --- hiddenFolders ---
229
2/2
✓ Branch 118 → 119 taken 4 times.
✓ Branch 118 → 123 taken 59 times.
126 if (p.enabledCategories.contains(QStringLiteral("hiddenFolders")))
230
1/1
✓ Branch 119 → 120 taken 4 times.
4 p.hiddenFolders = FolderPreferences::loadHidden(configDir);
231
232 // The external-content whitelist is intentionally local-only. A visible
233 // From header is spoofable, and an unsigned sync message must not expand
234 // the set of messages that may load remote resources automatically.
235
236 // --- carddavAccounts ---
237
2/2
✓ Branch 129 → 130 taken 6 times.
✓ Branch 129 → 188 taken 57 times.
126 if (p.enabledCategories.contains(QStringLiteral("davAccounts"))) {
238
1/1
✓ Branch 134 → 135 taken 6 times.
6 int count = s.beginReadArray(QStringLiteral("carddav/accounts"));
239
2/2
✓ Branch 186 → 138 taken 2 times.
✓ Branch 186 → 187 taken 6 times.
8 for (int i = 0; i < count; ++i) {
240
1/1
✓ Branch 138 → 139 taken 2 times.
2 s.setArrayIndex(i);
241 2 SyncPayload::CardDavAccount acc;
242
2/2
✓ Branch 143 → 144 taken 2 times.
✓ Branch 144 → 145 taken 2 times.
2 acc.id = s.value(QStringLiteral("id")).toString();
243
2/2
✓ Branch 154 → 155 taken 2 times.
✓ Branch 155 → 156 taken 2 times.
2 acc.serverUrl = s.value(QStringLiteral("serverUrl")).toString();
244
2/2
✓ Branch 165 → 166 taken 2 times.
✓ Branch 166 → 167 taken 2 times.
2 acc.username = s.value(QStringLiteral("username")).toString();
245 // NOTE: password intentionally NOT collected (security)
246
2/2
✓ Branch 176 → 177 taken 2 times.
✓ Branch 177 → 178 taken 2 times.
2 acc.selectedBooks = davPathList(s.value(QStringLiteral("selectedBooks")));
247
1/1
✓ Branch 183 → 184 taken 2 times.
2 p.carddavAccounts.append(acc);
248 2 }
249
1/1
✓ Branch 187 → 188 taken 6 times.
6 s.endArray();
250 }
251
252 // --- caldavConfigs (T-334: references CardDAV accounts) ---
253
2/2
✓ Branch 194 → 195 taken 6 times.
✓ Branch 194 → 253 taken 57 times.
126 if (p.enabledCategories.contains(QStringLiteral("davAccounts"))) {
254
1/1
✓ Branch 199 → 200 taken 6 times.
6 int caCount = s.beginReadArray(QStringLiteral("caldav/configs"));
255
2/2
✓ Branch 251 → 203 taken 2 times.
✓ Branch 251 → 252 taken 6 times.
8 for (int i = 0; i < caCount; ++i) {
256
1/1
✓ Branch 203 → 204 taken 2 times.
2 s.setArrayIndex(i);
257 2 SyncPayload::CalDavSyncConfig cfg;
258
2/2
✓ Branch 208 → 209 taken 2 times.
✓ Branch 209 → 210 taken 2 times.
2 cfg.carddavAccountId = s.value(QStringLiteral("carddavAccountId")).toString();
259
2/2
✓ Branch 219 → 220 taken 2 times.
✓ Branch 220 → 221 taken 2 times.
2 cfg.selectedCalendars = davPathList(s.value(QStringLiteral("selectedCalendars")));
260
2/2
✓ Branch 230 → 231 taken 2 times.
✓ Branch 231 → 232 taken 2 times.
2 cfg.readOnlyCalendars = davPathList(s.value(QStringLiteral("readOnlyCalendars")));
261
2/2
✓ Branch 242 → 243 taken 2 times.
✓ Branch 243 → 244 taken 2 times.
4 cfg.syncIntervalMinutes = s.value(QStringLiteral("syncIntervalMin"), 15).toInt();
262
1/1
✓ Branch 248 → 249 taken 2 times.
2 p.caldavConfigs.append(cfg);
263 2 }
264
1/1
✓ Branch 252 → 253 taken 6 times.
6 s.endArray();
265 }
266
267 // --- calendarColors (from CalendarStore SQLite) ---
268
10/10
✓ Branch 253 → 254 taken 8 times.
✓ Branch 253 → 260 taken 55 times.
✓ Branch 258 → 259 taken 2 times.
✓ Branch 258 → 260 taken 6 times.
✓ Branch 261 → 262 taken 8 times.
✓ Branch 261 → 263 taken 55 times.
✓ Branch 263 → 264 taken 8 times.
✓ Branch 263 → 265 taken 55 times.
✓ Branch 265 → 266 taken 2 times.
✓ Branch 265 → 284 taken 61 times.
71 if (calStore && p.enabledCategories.contains(QStringLiteral("calendarColors"))) {
269
1/1
✓ Branch 266 → 267 taken 2 times.
2 const auto calendars = calStore->allCalendars();
270
2/2
✓ Branch 281 → 269 taken 3 times.
✓ Branch 281 → 282 taken 2 times.
5 for (const auto &cal : calendars) {
271
1/1
✓ Branch 270 → 271 taken 3 times.
3 QString color = calStore->calendarColor(cal.accountId, cal.path);
272
1/2
✓ Branch 272 → 273 taken 3 times.
✗ Branch 272 → 278 not taken.
3 if (!color.isEmpty())
273
2/2
✓ Branch 273 → 274 taken 3 times.
✓ Branch 274 → 275 taken 3 times.
3 p.calendarColors[calendarColorKey(cal.accountId, cal.path)] = color;
274 3 }
275 2 }
276
277 // --- general ---
278
2/2
✓ Branch 290 → 291 taken 58 times.
✓ Branch 290 → 368 taken 5 times.
126 if (p.enabledCategories.contains(QStringLiteral("general"))) {
279 p.general.defaultView =
280
1/1
✓ Branch 299 → 300 taken 58 times.
174 s.value(QStringLiteral("view/defaultMode"), QStringLiteral("text"))
281
1/1
✓ Branch 300 → 301 taken 58 times.
116 .toString();
282 p.general.externalContent =
283
1/1
✓ Branch 317 → 318 taken 58 times.
174 s.value(QStringLiteral("view/externalContent"), QStringLiteral("block"))
284
1/1
✓ Branch 318 → 319 taken 58 times.
116 .toString();
285 p.general.language =
286
1/1
✓ Branch 335 → 336 taken 58 times.
174 s.value(QStringLiteral("i18n/language"), QStringLiteral("auto"))
287
1/1
✓ Branch 336 → 337 taken 58 times.
58 .toString();
288 58 p.general.carddavSyncInterval =
289
2/2
✓ Branch 350 → 351 taken 58 times.
✓ Branch 351 → 352 taken 58 times.
116 s.value(QStringLiteral("carddav/syncIntervalMin"), 0).toInt();
290 58 p.general.caldavSyncInterval =
291
2/2
✓ Branch 361 → 362 taken 58 times.
✓ Branch 362 → 363 taken 58 times.
116 s.value(QStringLiteral("caldav/syncIntervalMin"), 15).toInt();
292 }
293
294 // --- identities, templates (Sprint 108, ComposeSettingsSync) ---
295
2/2
✓ Branch 374 → 375 taken 4 times.
✓ Branch 374 → 393 taken 59 times.
126 if (p.enabledCategories.contains(QStringLiteral("identities"))) {
296
1/1
✓ Branch 375 → 376 taken 4 times.
4 IdentityStore identities(IdentityStore::pathFor(configDir));
297
3/4
✓ Branch 378 → 379 taken 4 times.
✓ Branch 379 → 380 taken 4 times.
✗ Branch 379 → 384 not taken.
✓ Branch 380 → 381 taken 4 times.
4 if (identities.load()) p.identities = ComposeSettingsSync::collectIdentities(identities);
298 ✗ else p.enabledCategories.removeAll(QStringLiteral("identities"));
299 4 }
300
2/2
✓ Branch 399 → 400 taken 4 times.
✓ Branch 399 → 418 taken 59 times.
126 if (p.enabledCategories.contains(QStringLiteral("templates"))) {
301
1/1
✓ Branch 400 → 401 taken 4 times.
4 TemplateStore templates(TemplateStore::pathFor(configDir));
302
3/4
✓ Branch 403 → 404 taken 4 times.
✓ Branch 404 → 405 taken 4 times.
✗ Branch 404 → 409 not taken.
✓ Branch 405 → 406 taken 4 times.
4 if (templates.load()) p.templates = ComposeSettingsSync::collectTemplates(templates);
303 ✗ else p.enabledCategories.removeAll(QStringLiteral("templates"));
304 4 }
305 // The sync message has a size limit: templates, then identities, stay
306 // out of an upload that would exceed it (never half of them — a missing
307 // template would read as deleted on the other installation).
308
1/2
✓ Branch 452 → 428 taken 63 times.
✗ Branch 452 → 453 not taken.
378 for (const QString &category : {QStringLiteral("templates"), QStringLiteral("identities")}) {
309
2/3
✓ Branch 428 → 429 taken 63 times.
✓ Branch 431 → 432 taken 63 times.
✗ Branch 431 → 433 not taken.
63 if (p.toRfcMessage().size() <= SyncPayload::MaxRfcMessageBytes) break;
310 ✗ p.enabledCategories.removeAll(category);
311 ✗ (category == QLatin1String("templates") ? p.templates : p.identities).clear();
312 ✗ qCWarning(lcSettingsSync) << "Left out of the upload, too large:" << category;
313 189 }
314
315 63 return p;
316 63 }
317
318 // ═══════════════════════════════════════════════════════
319 // applyRemote – apply a remote SyncPayload to local settings
320 // ═══════════════════════════════════════════════════════
321
322 16 QStringList SettingsCollector::applyRemote(const SyncPayload &payload,
323 MailCache *cache,
324 const QString &configDir,
325 CalendarStore *calStore) {
326
1/1
✓ Branch 2 → 3 taken 16 times.
16 QSettings s;
327
1/1
✓ Branch 3 → 4 taken 16 times.
16 const QStringList cats = effectiveRemoteCategories(payload, s);
328 16 QStringList applied;
329
330
5/5
✓ Branch 4 → 5 taken 16 times.
✓ Branch 8 → 9 taken 16 times.
✓ Branch 9 → 10 taken 16 times.
✓ Branch 21 → 6 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
32 qCInfo(lcSettingsSync) << "Accepted remote settings categories:"
331
2/2
✓ Branch 13 → 14 taken 16 times.
✓ Branch 14 → 15 taken 16 times.
16 << cats.join(QStringLiteral(", "));
332
333 // --- folderIcons ---
334
6/12
✓ Branch 26 → 27 taken 8 times.
✓ Branch 26 → 30 taken 8 times.
✓ Branch 31 → 32 taken 16 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 16 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 8 times.
✓ Branch 35 → 115 taken 8 times.
✗ Branch 897 → 898 not taken.
✗ Branch 897 → 899 not taken.
✗ Branch 901 → 902 not taken.
✗ Branch 901 → 903 not taken.
40 if (cats.contains(QStringLiteral("folderIcons")) &&
335
2/3
✓ Branch 27 → 28 taken 8 times.
✓ Branch 28 → 29 taken 8 times.
✗ Branch 28 → 30 not taken.
8 payload.folderIcons.size() <= kMaxSyncedMapEntries) {
336 // Clear existing folder icons
337
1/1
✓ Branch 40 → 41 taken 8 times.
8 s.beginGroup(QStringLiteral("folder/icon"));
338
1/1
✓ Branch 45 → 46 taken 8 times.
8 s.remove(QString()); // removes all keys in the group
339
1/1
✓ Branch 47 → 48 taken 8 times.
8 s.endGroup();
340 // Write new ones
341
1/1
✓ Branch 48 → 49 taken 8 times.
8 for (auto it = payload.folderIcons.constBegin();
342
3/3
✓ Branch 92 → 93 taken 16 times.
✓ Branch 94 → 50 taken 8 times.
✓ Branch 94 → 95 taken 8 times.
16 it != payload.folderIcons.constEnd(); ++it) {
343
1/2
✓ Branch 52 → 53 taken 8 times.
✗ Branch 52 → 60 not taken.
16 if (safeText(it.key(), 4096) &&
344
8/11
✓ Branch 53 → 54 taken 8 times.
✓ Branch 56 → 57 taken 8 times.
✓ Branch 57 → 58 taken 8 times.
✓ Branch 58 → 59 taken 7 times.
✓ Branch 58 → 60 taken 1 time.
✓ Branch 61 → 62 taken 8 times.
✗ Branch 61 → 63 not taken.
✓ Branch 63 → 64 taken 7 times.
✓ Branch 63 → 78 taken 1 time.
✗ Branch 916 → 917 not taken.
✗ Branch 916 → 918 not taken.
16 iconNamePattern().match(it.value()).hasMatch()) {
345
2/2
✓ Branch 70 → 71 taken 7 times.
✓ Branch 72 → 73 taken 7 times.
14 s.setValue(QStringLiteral("folder/icon/") + it.key(), it.value());
346 } else {
347
5/5
✓ Branch 78 → 79 taken 1 time.
✓ Branch 82 → 83 taken 1 time.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 89 → 80 taken 1 time.
✓ Branch 89 → 90 taken 1 time.
2 qCWarning(lcSettingsSync) << "Rejected invalid folder icon mapping for"
348
1/1
✓ Branch 85 → 86 taken 1 time.
1 << it.key();
349 }
350 }
351
7/7
✓ Branch 95 → 96 taken 8 times.
✓ Branch 99 → 100 taken 8 times.
✓ Branch 100 → 101 taken 8 times.
✓ Branch 101 → 102 taken 8 times.
✓ Branch 102 → 103 taken 8 times.
✓ Branch 107 → 97 taken 8 times.
✓ Branch 107 → 108 taken 8 times.
16 qCInfo(lcSettingsSync) << "Applied" << payload.folderIcons.size()
352
1/1
✓ Branch 103 → 104 taken 8 times.
8 << "folder icons";
353
1/1
✓ Branch 111 → 112 taken 8 times.
8 applied.append(QStringLiteral("folderIcons"));
354 }
355
356 // --- folderColors ---
357
6/12
✓ Branch 119 → 120 taken 2 times.
✓ Branch 119 → 123 taken 14 times.
✓ Branch 124 → 125 taken 16 times.
✗ Branch 124 → 126 not taken.
✓ Branch 126 → 127 taken 16 times.
✗ Branch 126 → 128 not taken.
✓ Branch 128 → 129 taken 2 times.
✓ Branch 128 → 208 taken 14 times.
✗ Branch 952 → 953 not taken.
✗ Branch 952 → 954 not taken.
✗ Branch 956 → 957 not taken.
✗ Branch 956 → 958 not taken.
34 if (cats.contains(QStringLiteral("folderColors")) &&
358
2/3
✓ Branch 120 → 121 taken 2 times.
✓ Branch 121 → 122 taken 2 times.
✗ Branch 121 → 123 not taken.
2 payload.folderColors.size() <= kMaxSyncedMapEntries) {
359
1/1
✓ Branch 133 → 134 taken 2 times.
2 s.beginGroup(QStringLiteral("folder/color"));
360
1/1
✓ Branch 138 → 139 taken 2 times.
2 s.remove(QString());
361
1/1
✓ Branch 140 → 141 taken 2 times.
2 s.endGroup();
362
1/1
✓ Branch 141 → 142 taken 2 times.
2 for (auto it = payload.folderColors.constBegin();
363
3/3
✓ Branch 185 → 186 taken 5 times.
✓ Branch 187 → 143 taken 3 times.
✓ Branch 187 → 188 taken 2 times.
5 it != payload.folderColors.constEnd(); ++it) {
364
1/2
✓ Branch 145 → 146 taken 3 times.
✗ Branch 145 → 153 not taken.
6 if (safeText(it.key(), 4096) &&
365
8/11
✓ Branch 146 → 147 taken 3 times.
✓ Branch 149 → 150 taken 3 times.
✓ Branch 150 → 151 taken 3 times.
✓ Branch 151 → 152 taken 2 times.
✓ Branch 151 → 153 taken 1 time.
✓ Branch 154 → 155 taken 3 times.
✗ Branch 154 → 156 not taken.
✓ Branch 156 → 157 taken 2 times.
✓ Branch 156 → 171 taken 1 time.
✗ Branch 971 → 972 not taken.
✗ Branch 971 → 973 not taken.
6 colorPattern().match(it.value()).hasMatch()) {
366
2/2
✓ Branch 163 → 164 taken 2 times.
✓ Branch 165 → 166 taken 2 times.
4 s.setValue(QStringLiteral("folder/color/") + it.key(), it.value());
367 } else {
368
5/5
✓ Branch 171 → 172 taken 1 time.
✓ Branch 175 → 176 taken 1 time.
✓ Branch 176 → 177 taken 1 time.
✓ Branch 182 → 173 taken 1 time.
✓ Branch 182 → 183 taken 1 time.
2 qCWarning(lcSettingsSync) << "Rejected invalid folder color mapping for"
369
1/1
✓ Branch 178 → 179 taken 1 time.
1 << it.key();
370 }
371 }
372
7/7
✓ Branch 188 → 189 taken 2 times.
✓ Branch 192 → 193 taken 2 times.
✓ Branch 193 → 194 taken 2 times.
✓ Branch 194 → 195 taken 2 times.
✓ Branch 195 → 196 taken 2 times.
✓ Branch 200 → 190 taken 2 times.
✓ Branch 200 → 201 taken 2 times.
4 qCInfo(lcSettingsSync) << "Applied" << payload.folderColors.size()
373
1/1
✓ Branch 196 → 197 taken 2 times.
2 << "folder colors";
374
1/1
✓ Branch 204 → 205 taken 2 times.
2 applied.append(QStringLiteral("folderColors"));
375 }
376
377 // --- hiddenFolders ---
378
2/2
✓ Branch 214 → 215 taken 2 times.
✓ Branch 214 → 257 taken 14 times.
16 if (cats.contains(QStringLiteral("hiddenFolders"))) {
379 2 QStringList hidden;
380
1/1
✓ Branch 217 → 218 taken 2 times.
2 hidden.reserve(qMin(payload.hiddenFolders.size(), qsizetype{4096}));
381
2/2
✓ Branch 234 → 220 taken 5 times.
✓ Branch 234 → 235 taken 2 times.
7 for (const QString &folder : payload.hiddenFolders) {
382
1/2
✗ Branch 222 → 223 not taken.
✓ Branch 222 → 224 taken 5 times.
5 if (hidden.size() >= 4096)
383 ✗ break;
384
6/6
✓ Branch 225 → 226 taken 3 times.
✓ Branch 225 → 229 taken 2 times.
✓ Branch 227 → 228 taken 2 times.
✓ Branch 227 → 229 taken 1 time.
✓ Branch 230 → 231 taken 2 times.
✓ Branch 230 → 232 taken 3 times.
5 if (safeText(folder, 4096) && !hidden.contains(folder))
385
1/1
✓ Branch 231 → 232 taken 2 times.
2 hidden.append(folder);
386 }
387
1/1
✓ Branch 235 → 236 taken 2 times.
2 FolderPreferences::saveHidden(configDir, hidden);
388
6/6
✓ Branch 236 → 237 taken 2 times.
✓ Branch 240 → 241 taken 2 times.
✓ Branch 241 → 242 taken 2 times.
✓ Branch 243 → 244 taken 2 times.
✓ Branch 248 → 238 taken 2 times.
✓ Branch 248 → 249 taken 2 times.
4 qCInfo(lcSettingsSync) << "Applied" << hidden.size()
389
1/1
✓ Branch 244 → 245 taken 2 times.
2 << "hidden folders";
390
1/1
✓ Branch 252 → 253 taken 2 times.
2 applied.append(QStringLiteral("hiddenFolders"));
391 2 }
392
393 Q_UNUSED(cache);
394
395 // --- carddavAccounts ---
396 16 QSet<QString> acceptedDavIds;
397
2/2
✓ Branch 264 → 265 taken 6 times.
✓ Branch 264 → 450 taken 10 times.
16 if (cats.contains(QStringLiteral("davAccounts"))) {
398 QList<QVariantMap> localAccounts =
399
1/1
✓ Branch 268 → 269 taken 6 times.
6 readSettingsArray(s, QStringLiteral("carddav/accounts"));
400 6 QHash<QString, int> localById;
401
2/2
✓ Branch 287 → 272 taken 5 times.
✓ Branch 287 → 288 taken 6 times.
11 for (int i = 0; i < localAccounts.size(); ++i)
402
4/4
✓ Branch 272 → 273 taken 5 times.
✓ Branch 277 → 278 taken 5 times.
✓ Branch 278 → 279 taken 5 times.
✓ Branch 279 → 280 taken 5 times.
10 localById.insert(localAccounts[i].value(QStringLiteral("id")).toString(),
403 i);
404
405 6 bool changed = false;
406 6 QSet<QString> seenRemoteIds;
407
2/2
✓ Branch 418 → 291 taken 8 times.
✓ Branch 418 → 419 taken 6 times.
14 for (const auto &remote : payload.carddavAccounts) {
408
1/1
✓ Branch 292 → 293 taken 8 times.
8 const QString remoteId = remote.id.trimmed();
409
7/7
✓ Branch 293 → 294 taken 8 times.
✓ Branch 294 → 295 taken 7 times.
✓ Branch 294 → 297 taken 1 time.
✓ Branch 296 → 297 taken 1 time.
✓ Branch 296 → 298 taken 6 times.
✓ Branch 299 → 300 taken 2 times.
✓ Branch 299 → 312 taken 6 times.
8 if (!validDavAccount(remote) || seenRemoteIds.contains(remoteId)) {
410
5/5
✓ Branch 300 → 301 taken 2 times.
✓ Branch 304 → 305 taken 2 times.
✓ Branch 305 → 306 taken 2 times.
✓ Branch 310 → 302 taken 2 times.
✓ Branch 310 → 311 taken 2 times.
4 qCWarning(lcSettingsSync) << "Rejected invalid or duplicate DAV account"
411
1/1
✓ Branch 306 → 307 taken 2 times.
2 << remoteId;
412 2 continue;
413 2 }
414
1/1
✓ Branch 312 → 313 taken 6 times.
6 seenRemoteIds.insert(remoteId);
415
416 6 const auto localIt = localById.constFind(remoteId);
417
2/2
✓ Branch 316 → 317 taken 4 times.
✓ Branch 316 → 345 taken 2 times.
6 if (localIt != localById.cend()) {
418
1/1
✓ Branch 318 → 319 taken 4 times.
4 QVariantMap &local = localAccounts[*localIt];
419
3/3
✓ Branch 319 → 320 taken 4 times.
✓ Branch 320 → 321 taken 1 time.
✓ Branch 320 → 333 taken 3 times.
4 if (!sameDavIdentity(local, remote)) {
420
4/4
✓ Branch 321 → 322 taken 1 time.
✓ Branch 325 → 326 taken 1 time.
✓ Branch 331 → 323 taken 1 time.
✓ Branch 331 → 332 taken 1 time.
2 qCWarning(lcSettingsSync)
421
2/2
✓ Branch 326 → 327 taken 1 time.
✓ Branch 327 → 328 taken 1 time.
1 << "Rejected remote DAV identity change for account" << remoteId;
422 1 continue;
423 1 }
424
1/1
✓ Branch 338 → 339 taken 3 times.
6 local.insert(QStringLiteral("selectedBooks"),
425
1/1
✓ Branch 333 → 334 taken 3 times.
6 safeDavSelections(remote.selectedBooks));
426
1/1
✓ Branch 343 → 344 taken 3 times.
3 acceptedDavIds.insert(remoteId);
427 3 changed = true;
428 3 continue;
429 3 }
430
431
1/2
✗ Branch 346 → 347 not taken.
✓ Branch 346 → 358 taken 2 times.
2 if (localAccounts.size() >= kMaxDavAccounts) {
432 ✗ qCWarning(lcSettingsSync) << "DAV account limit reached";
433 ✗ break;
434 }
435 2 QVariantMap pending;
436
1/1
✓ Branch 362 → 363 taken 2 times.
4 pending.insert(QStringLiteral("id"), remoteId);
437
2/2
✓ Branch 366 → 367 taken 2 times.
✓ Branch 371 → 372 taken 2 times.
4 pending.insert(QStringLiteral("serverUrl"), remote.serverUrl.trimmed());
438
2/2
✓ Branch 376 → 377 taken 2 times.
✓ Branch 381 → 382 taken 2 times.
4 pending.insert(QStringLiteral("username"), remote.username.trimmed());
439
1/1
✓ Branch 391 → 392 taken 2 times.
4 pending.insert(QStringLiteral("selectedBooks"),
440
1/1
✓ Branch 386 → 387 taken 2 times.
4 safeDavSelections(remote.selectedBooks));
441
1/1
✓ Branch 400 → 401 taken 2 times.
4 pending.insert(QStringLiteral("requiresLocalAuthorization"), true);
442
1/1
✓ Branch 404 → 405 taken 2 times.
2 localAccounts.append(pending);
443
1/1
✓ Branch 406 → 407 taken 2 times.
2 localById.insert(remoteId, localAccounts.size() - 1);
444
1/1
✓ Branch 407 → 408 taken 2 times.
2 acceptedDavIds.insert(remoteId);
445 2 changed = true;
446 8 }
447
448
2/2
✓ Branch 419 → 420 taken 4 times.
✓ Branch 419 → 446 taken 2 times.
6 if (changed) {
449
1/1
✓ Branch 423 → 424 taken 4 times.
4 writeSettingsArray(s, QStringLiteral("carddav/accounts"), localAccounts);
450
1/1
✓ Branch 429 → 430 taken 4 times.
4 applied.append(QStringLiteral("davAccounts"));
451
6/6
✓ Branch 432 → 433 taken 4 times.
✓ Branch 436 → 437 taken 4 times.
✓ Branch 437 → 438 taken 4 times.
✓ Branch 439 → 440 taken 4 times.
✓ Branch 444 → 434 taken 4 times.
✓ Branch 444 → 445 taken 4 times.
8 qCInfo(lcSettingsSync) << "Merged" << acceptedDavIds.size()
452 << "DAV account selections without changing"
453
1/1
✓ Branch 440 → 441 taken 4 times.
4 " local endpoint identities";
454 }
455 6 }
456
457 // --- caldavConfigs (T-334: calendar selection per CardDAV account) ---
458
6/8
✓ Branch 454 → 455 taken 6 times.
✓ Branch 454 → 458 taken 10 times.
✓ Branch 459 → 460 taken 16 times.
✗ Branch 459 → 461 not taken.
✓ Branch 461 → 462 taken 16 times.
✗ Branch 461 → 463 not taken.
✓ Branch 463 → 464 taken 4 times.
✓ Branch 463 → 590 taken 12 times.
38 if (cats.contains(QStringLiteral("davAccounts")) &&
459
2/2
✓ Branch 456 → 457 taken 4 times.
✓ Branch 456 → 458 taken 2 times.
6 !acceptedDavIds.isEmpty()) {
460 QList<QVariantMap> localConfigs =
461
1/1
✓ Branch 467 → 468 taken 4 times.
4 readSettingsArray(s, QStringLiteral("caldav/configs"));
462 4 QHash<QString, int> localConfigById;
463
2/2
✓ Branch 486 → 471 taken 1 time.
✓ Branch 486 → 487 taken 4 times.
5 for (int i = 0; i < localConfigs.size(); ++i) {
464
1/1
✓ Branch 478 → 479 taken 1 time.
1 localConfigById.insert(
465
1/1
✓ Branch 471 → 472 taken 1 time.
1 localConfigs[i]
466
1/1
✓ Branch 476 → 477 taken 1 time.
3 .value(QStringLiteral("carddavAccountId"))
467
1/1
✓ Branch 477 → 478 taken 1 time.
3 .toString(),
468 i);
469 }
470
471 4 bool configsChanged = false;
472
2/2
✓ Branch 578 → 489 taken 3 times.
✓ Branch 578 → 579 taken 4 times.
7 for (const auto &remote : payload.caldavConfigs) {
473
1/1
✓ Branch 490 → 491 taken 3 times.
3 const QString accountId = remote.carddavAccountId.trimmed();
474
2/2
✓ Branch 492 → 493 taken 1 time.
✓ Branch 492 → 494 taken 2 times.
3 if (!acceptedDavIds.contains(accountId))
475 1 continue;
476
1/1
✓ Branch 494 → 495 taken 2 times.
2 const QStringList selected = safeDavSelections(remote.selectedCalendars);
477
1/1
✓ Branch 495 → 496 taken 2 times.
2 QStringList readOnly = safeDavSelections(remote.readOnlyCalendars);
478
4/4
✓ Branch 496 → 497 taken 2 times.
✓ Branch 505 → 506 taken 4 times.
✓ Branch 507 → 498 taken 2 times.
✓ Branch 507 → 508 taken 2 times.
4 for (auto it = readOnly.begin(); it != readOnly.end();) {
479
2/2
✓ Branch 500 → 501 taken 1 time.
✓ Branch 500 → 504 taken 1 time.
2 if (!selected.contains(*it))
480
1/1
✓ Branch 502 → 503 taken 1 time.
1 it = readOnly.erase(it);
481 else
482 1 ++it;
483 }
484
485 2 auto localIt = localConfigById.constFind(accountId);
486
2/2
✓ Branch 511 → 512 taken 1 time.
✓ Branch 511 → 549 taken 1 time.
2 if (localIt == localConfigById.cend()) {
487 1 QVariantMap config;
488
1/1
✓ Branch 516 → 517 taken 1 time.
2 config.insert(QStringLiteral("carddavAccountId"), accountId);
489
1/1
✓ Branch 524 → 525 taken 1 time.
2 config.insert(QStringLiteral("selectedCalendars"), selected);
490
1/1
✓ Branch 532 → 533 taken 1 time.
2 config.insert(QStringLiteral("readOnlyCalendars"), readOnly);
491 // Scheduling remains a local policy; do not copy the remote interval.
492
1/1
✓ Branch 540 → 541 taken 1 time.
2 config.insert(QStringLiteral("syncIntervalMin"), 15);
493
1/1
✓ Branch 544 → 545 taken 1 time.
1 localConfigs.append(config);
494
1/1
✓ Branch 546 → 547 taken 1 time.
1 localConfigById.insert(accountId, localConfigs.size() - 1);
495 1 } else {
496
1/1
✓ Branch 550 → 551 taken 1 time.
1 QVariantMap &config = localConfigs[*localIt];
497
1/1
✓ Branch 555 → 556 taken 1 time.
2 config.insert(QStringLiteral("selectedCalendars"), selected);
498
1/1
✓ Branch 563 → 564 taken 1 time.
2 config.insert(QStringLiteral("readOnlyCalendars"), readOnly);
499 }
500 2 configsChanged = true;
501 3 }
502
2/2
✓ Branch 579 → 580 taken 1 time.
✓ Branch 579 → 587 taken 3 times.
4 if (configsChanged)
503
1/1
✓ Branch 583 → 584 taken 1 time.
1 writeSettingsArray(s, QStringLiteral("caldav/configs"), localConfigs);
504 4 }
505
506 // --- calendarColors (to CalendarStore SQLite) ---
507
8/14
✓ Branch 594 → 595 taken 2 times.
✓ Branch 594 → 599 taken 14 times.
✓ Branch 595 → 596 taken 1 time.
✓ Branch 595 → 599 taken 1 time.
✓ Branch 600 → 601 taken 16 times.
✗ Branch 600 → 602 not taken.
✓ Branch 602 → 603 taken 16 times.
✗ Branch 602 → 604 not taken.
✓ Branch 604 → 605 taken 1 time.
✓ Branch 604 → 658 taken 15 times.
✗ Branch 1262 → 1263 not taken.
✗ Branch 1262 → 1264 not taken.
✗ Branch 1266 → 1267 not taken.
✗ Branch 1266 → 1268 not taken.
33 if (cats.contains(QStringLiteral("calendarColors")) && calStore &&
508
2/3
✓ Branch 596 → 597 taken 1 time.
✓ Branch 597 → 598 taken 1 time.
✗ Branch 597 → 599 not taken.
1 payload.calendarColors.size() <= kMaxSyncedMapEntries) {
509
1/1
✓ Branch 605 → 606 taken 1 time.
1 for (auto it = payload.calendarColors.constBegin();
510
3/3
✓ Branch 635 → 636 taken 4 times.
✓ Branch 637 → 607 taken 3 times.
✓ Branch 637 → 638 taken 1 time.
4 it != payload.calendarColors.constEnd(); ++it) {
511 3 QString accountId;
512 3 QString calendarPath;
513 const bool accountAware =
514
1/1
✓ Branch 608 → 609 taken 3 times.
3 decodeCalendarColorKey(it.key(), &accountId, &calendarPath);
515
2/2
✓ Branch 609 → 610 taken 1 time.
✓ Branch 609 → 611 taken 2 times.
3 const QString validatedPath = accountAware ? calendarPath : it.key();
516
1/2
✓ Branch 614 → 615 taken 3 times.
✗ Branch 614 → 622 not taken.
6 if (safeText(validatedPath, 4096) &&
517
8/11
✓ Branch 615 → 616 taken 3 times.
✓ Branch 618 → 619 taken 3 times.
✓ Branch 619 → 620 taken 3 times.
✓ Branch 620 → 621 taken 2 times.
✓ Branch 620 → 622 taken 1 time.
✓ Branch 623 → 624 taken 3 times.
✗ Branch 623 → 625 not taken.
✓ Branch 625 → 626 taken 2 times.
✓ Branch 625 → 631 taken 1 time.
✗ Branch 1270 → 1271 not taken.
✗ Branch 1270 → 1272 not taken.
6 colorPattern().match(it.value()).hasMatch()) {
518
2/2
✓ Branch 626 → 627 taken 1 time.
✓ Branch 626 → 629 taken 1 time.
2 if (accountAware)
519
1/1
✓ Branch 628 → 631 taken 1 time.
1 calStore->setCalendarColor(accountId, calendarPath, it.value());
520 else
521
1/1
✓ Branch 630 → 631 taken 1 time.
1 calStore->setCalendarColor(validatedPath, it.value());
522 }
523 3 }
524
7/7
✓ Branch 638 → 639 taken 1 time.
✓ Branch 642 → 643 taken 1 time.
✓ Branch 643 → 644 taken 1 time.
✓ Branch 644 → 645 taken 1 time.
✓ Branch 645 → 646 taken 1 time.
✓ Branch 650 → 640 taken 1 time.
✓ Branch 650 → 651 taken 1 time.
2 qCInfo(lcSettingsSync) << "Applied" << payload.calendarColors.size()
525
1/1
✓ Branch 646 → 647 taken 1 time.
1 << "calendar colors";
526
1/1
✓ Branch 654 → 655 taken 1 time.
1 applied.append(QStringLiteral("calendarColors"));
527 }
528
529 // --- general ---
530
2/2
✓ Branch 664 → 665 taken 10 times.
✓ Branch 664 → 763 taken 6 times.
16 if (cats.contains(QStringLiteral("general"))) {
531 10 bool generalApplied = false;
532
5/6
✓ Branch 674 → 675 taken 2 times.
✓ Branch 674 → 680 taken 6 times.
✓ Branch 692 → 693 taken 10 times.
✗ Branch 692 → 694 not taken.
✓ Branch 694 → 695 taken 9 times.
✓ Branch 694 → 705 taken 1 time.
28 if (payload.general.defaultView == QStringLiteral("text") ||
533
7/8
✓ Branch 669 → 670 taken 8 times.
✓ Branch 669 → 680 taken 2 times.
✓ Branch 679 → 680 taken 1 time.
✓ Branch 679 → 681 taken 1 time.
✓ Branch 688 → 689 taken 8 times.
✓ Branch 688 → 690 taken 2 times.
✓ Branch 690 → 691 taken 10 times.
✗ Branch 690 → 692 not taken.
46 payload.general.defaultView == QStringLiteral("light") ||
534
6/6
✓ Branch 682 → 683 taken 2 times.
✓ Branch 682 → 684 taken 8 times.
✓ Branch 684 → 685 taken 2 times.
✓ Branch 684 → 686 taken 8 times.
✓ Branch 686 → 687 taken 8 times.
✓ Branch 686 → 688 taken 2 times.
12 payload.general.defaultView == QStringLiteral("html")) {
535
1/1
✓ Branch 700 → 701 taken 9 times.
9 s.setValue(QStringLiteral("view/defaultMode"),
536 9 payload.general.defaultView);
537 9 generalApplied = true;
538 }
539
4/6
✓ Branch 714 → 715 taken 4 times.
✗ Branch 714 → 720 not taken.
✓ Branch 732 → 733 taken 10 times.
✗ Branch 732 → 734 not taken.
✓ Branch 734 → 735 taken 9 times.
✓ Branch 734 → 745 taken 1 time.
24 if (payload.general.language == QStringLiteral("auto") ||
540
7/8
✓ Branch 709 → 710 taken 4 times.
✓ Branch 709 → 720 taken 6 times.
✓ Branch 719 → 720 taken 3 times.
✓ Branch 719 → 721 taken 1 time.
✓ Branch 728 → 729 taken 4 times.
✓ Branch 728 → 730 taken 6 times.
✓ Branch 730 → 731 taken 10 times.
✗ Branch 730 → 732 not taken.
38 payload.general.language == QStringLiteral("en") ||
541
6/6
✓ Branch 722 → 723 taken 4 times.
✓ Branch 722 → 724 taken 6 times.
✓ Branch 724 → 725 taken 4 times.
✓ Branch 724 → 726 taken 6 times.
✓ Branch 726 → 727 taken 4 times.
✓ Branch 726 → 728 taken 6 times.
14 payload.general.language == QStringLiteral("de")) {
542
1/1
✓ Branch 740 → 741 taken 9 times.
18 s.setValue(QStringLiteral("i18n/language"), payload.general.language);
543 9 generalApplied = true;
544 }
545 // view/externalContent and all network scheduling intervals stay local.
546
2/2
✓ Branch 745 → 746 taken 9 times.
✓ Branch 745 → 763 taken 1 time.
10 if (generalApplied) {
547
1/1
✓ Branch 749 → 750 taken 9 times.
9 applied.append(QStringLiteral("general"));
548
5/5
✓ Branch 752 → 753 taken 9 times.
✓ Branch 756 → 757 taken 9 times.
✓ Branch 757 → 758 taken 9 times.
✓ Branch 761 → 754 taken 9 times.
✓ Branch 761 → 762 taken 9 times.
18 qCInfo(lcSettingsSync) << "Applied non-sensitive general settings";
549 }
550 }
551
552 // --- identities, templates: the composer's own files ---
553
2/2
✓ Branch 769 → 770 taken 1 time.
✓ Branch 769 → 801 taken 15 times.
16 if (cats.contains(QStringLiteral("identities"))) {
554
1/1
✓ Branch 770 → 771 taken 1 time.
1 IdentityStore identities(IdentityStore::pathFor(configDir));
555
7/11
✓ Branch 773 → 774 taken 1 time.
✓ Branch 774 → 775 taken 1 time.
✗ Branch 774 → 780 not taken.
✓ Branch 775 → 776 taken 1 time.
✓ Branch 776 → 777 taken 1 time.
✗ Branch 776 → 780 not taken.
✓ Branch 777 → 778 taken 1 time.
✓ Branch 778 → 779 taken 1 time.
✗ Branch 778 → 780 not taken.
✓ Branch 781 → 782 taken 1 time.
✗ Branch 781 → 799 not taken.
1 if (identities.load() && ComposeSettingsSync::applyIdentities(identities, payload.identities) && identities.save()) {
556
1/1
✓ Branch 785 → 786 taken 1 time.
1 applied.append(QStringLiteral("identities"));
557
5/5
✓ Branch 788 → 789 taken 1 time.
✓ Branch 792 → 793 taken 1 time.
✓ Branch 793 → 794 taken 1 time.
✓ Branch 797 → 790 taken 1 time.
✓ Branch 797 → 798 taken 1 time.
2 qCInfo(lcSettingsSync) << "Applied signatures and address settings";
558 }
559 1 }
560
2/2
✓ Branch 807 → 808 taken 1 time.
✓ Branch 807 → 842 taken 15 times.
16 if (cats.contains(QStringLiteral("templates"))) {
561
1/1
✓ Branch 808 → 809 taken 1 time.
1 TemplateStore templates(TemplateStore::pathFor(configDir));
562
7/11
✓ Branch 811 → 812 taken 1 time.
✓ Branch 812 → 813 taken 1 time.
✗ Branch 812 → 818 not taken.
✓ Branch 813 → 814 taken 1 time.
✓ Branch 814 → 815 taken 1 time.
✗ Branch 814 → 818 not taken.
✓ Branch 815 → 816 taken 1 time.
✓ Branch 816 → 817 taken 1 time.
✗ Branch 816 → 818 not taken.
✓ Branch 819 → 820 taken 1 time.
✗ Branch 819 → 840 not taken.
1 if (templates.load() && ComposeSettingsSync::applyTemplates(templates, payload.templates) && templates.save()) {
563
1/1
✓ Branch 823 → 824 taken 1 time.
1 applied.append(QStringLiteral("templates"));
564
6/6
✓ Branch 826 → 827 taken 1 time.
✓ Branch 830 → 831 taken 1 time.
✓ Branch 831 → 832 taken 1 time.
✓ Branch 834 → 835 taken 1 time.
✓ Branch 838 → 828 taken 1 time.
✓ Branch 838 → 839 taken 1 time.
2 qCInfo(lcSettingsSync) << "Applied templates" << templates.templates().size();
565 }
566 1 }
567
568
2/2
✓ Branch 843 → 844 taken 13 times.
✓ Branch 843 → 858 taken 3 times.
16 if (!applied.isEmpty()) {
569
1/1
✓ Branch 851 → 852 taken 13 times.
26 s.setValue(QStringLiteral("sync/lastSync"),
570
2/2
✓ Branch 844 → 845 taken 13 times.
✓ Branch 845 → 846 taken 13 times.
26 QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
571 }
572
573
1/1
✓ Branch 858 → 859 taken 16 times.
16 s.sync();
574
3/4
✓ Branch 860 → 861 taken 16 times.
✓ Branch 861 → 862 taken 16 times.
✗ Branch 863 → 864 not taken.
✓ Branch 863 → 878 taken 16 times.
16 if (!QFile::setPermissions(s.fileName(),
575 QFileDevice::ReadOwner | QFileDevice::WriteOwner)) {
576 ✗ qCWarning(lcSettingsSync) << "Failed to restrict settings permissions for"
577 ✗ << s.fileName();
578 }
579 16 return applied;
580 16 }
581
582 // ═══════════════════════════════════════════════════════
583 // categoryChanged – diff helper
584 // ═══════════════════════════════════════════════════════
585
586 33 bool SettingsCollector::categoryChanged(const QString &category,
587 const SyncPayload &local,
588 const SyncPayload &remote) {
589
2/2
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 11 taken 30 times.
33 if (category == QStringLiteral("folderIcons"))
590 3 return local.folderIcons != remote.folderIcons;
591
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 20 taken 28 times.
30 if (category == QStringLiteral("folderColors"))
592 2 return local.folderColors != remote.folderColors;
593
2/2
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 29 taken 26 times.
28 if (category == QStringLiteral("hiddenFolders"))
594 2 return local.hiddenFolders != remote.hiddenFolders;
595
2/2
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 57 taken 23 times.
26 if (category == QStringLiteral("externalContentWhitelist")) {
596
2/2
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 2 times.
6 if (local.externalContentWhitelist.size() !=
597 3 remote.externalContentWhitelist.size())
598 1 return true;
599
2/2
✓ Branch 55 → 41 taken 2 times.
✓ Branch 55 → 56 taken 1 time.
3 for (int i = 0; i < local.externalContentWhitelist.size(); ++i) {
600 8 if (local.externalContentWhitelist[i].type !=
601
3/4
✓ Branch 44 → 45 taken 2 times.
✗ Branch 44 → 49 not taken.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 1 time.
4 remote.externalContentWhitelist[i].type ||
602
2/2
✓ Branch 48 → 49 taken 1 time.
✓ Branch 48 → 50 taken 1 time.
2 local.externalContentWhitelist[i].value !=
603 2 remote.externalContentWhitelist[i].value)
604 1 return true;
605 }
606 1 return false;
607 }
608
2/2
✓ Branch 63 → 64 taken 2 times.
✓ Branch 63 → 66 taken 21 times.
23 if (category == QStringLiteral("calendarColors"))
609 2 return local.calendarColors != remote.calendarColors;
610
7/8
✓ Branch 70 → 71 taken 15 times.
✓ Branch 70 → 76 taken 6 times.
✓ Branch 75 → 76 taken 4 times.
✓ Branch 75 → 77 taken 11 times.
✓ Branch 84 → 85 taken 21 times.
✗ Branch 84 → 86 not taken.
✓ Branch 86 → 87 taken 10 times.
✓ Branch 86 → 167 taken 11 times.
78 if (category == QStringLiteral("davAccounts") ||
611
5/6
✓ Branch 78 → 79 taken 15 times.
✓ Branch 78 → 80 taken 6 times.
✓ Branch 80 → 81 taken 15 times.
✓ Branch 80 → 82 taken 6 times.
✓ Branch 82 → 83 taken 21 times.
✗ Branch 82 → 84 not taken.
36 category == QStringLiteral("carddavAccounts")) {
612 // F-73 (SPRINT-104): structural diff — compare the accounts and
613 // configs as id-keyed maps, order-insensitively. The former
614 // index-wise comparison reported a conflict forever for the same
615 // content stored in a different order, and neither resolution action
616 // could clear it (applyRemote merges by id and never reorders).
617 // syncIntervalMinutes is deliberately NOT compared: applyRemote never
618 // adopts it (scheduling is a local policy), so comparing it re-raised
619 // the conflict after every "use theirs".
620 // The raw list sizes catch duplicate-id entries the id map would
621 // silently deduplicate.
622
2/2
✓ Branch 89 → 90 taken 1 time.
✓ Branch 89 → 91 taken 9 times.
10 if (local.carddavAccounts.size() != remote.carddavAccounts.size())
623 1 return true;
624 9 QHash<QString, SyncPayload::CardDavAccount> localAccounts, remoteAccounts;
625
2/2
✓ Branch 97 → 93 taken 13 times.
✓ Branch 97 → 98 taken 9 times.
22 for (const auto &entry : local.carddavAccounts)
626
1/1
✓ Branch 94 → 95 taken 13 times.
13 localAccounts.insert(entry.id, entry);
627
2/2
✓ Branch 104 → 100 taken 13 times.
✓ Branch 104 → 105 taken 9 times.
22 for (const auto &entry : remote.carddavAccounts)
628
1/1
✓ Branch 101 → 102 taken 13 times.
13 remoteAccounts.insert(entry.id, entry);
629
1/2
✗ Branch 107 → 108 not taken.
✓ Branch 107 → 109 taken 9 times.
9 if (localAccounts.size() != remoteAccounts.size())
630 ✗ return true;
631
2/2
✓ Branch 136 → 110 taken 12 times.
✓ Branch 136 → 137 taken 6 times.
18 for (auto it = localAccounts.cbegin(); it != localAccounts.cend(); ++it) {
632 12 const auto other = remoteAccounts.constFind(it.key());
633
1/2
✗ Branch 114 → 115 not taken.
✓ Branch 114 → 116 taken 12 times.
12 if (other == remoteAccounts.cend())
634 3 return true;
635
4/6
✓ Branch 119 → 120 taken 12 times.
✗ Branch 119 → 128 not taken.
✓ Branch 123 → 124 taken 12 times.
✗ Branch 123 → 128 not taken.
✓ Branch 130 → 131 taken 3 times.
✓ Branch 130 → 132 taken 9 times.
24 if (it->serverUrl != other->serverUrl || it->username != other->username ||
636
3/3
✓ Branch 126 → 127 taken 12 times.
✓ Branch 127 → 128 taken 3 times.
✓ Branch 127 → 129 taken 9 times.
12 it->selectedBooks != other->selectedBooks)
637 3 return true;
638 }
639 // A config's identity is its account (1:1); compare the synced fields.
640
2/2
✓ Branch 139 → 140 taken 1 time.
✓ Branch 139 → 141 taken 5 times.
6 if (local.caldavConfigs.size() != remote.caldavConfigs.size())
641 1 return true;
642 7 const auto configFor = [](const QList<SyncPayload::CalDavSyncConfig> &list, const QString &accountId) {
643 7 return std::find_if(list.cbegin(), list.cend(),
644 9 [&accountId](const SyncPayload::CalDavSyncConfig &config) {
645 9 return config.carddavAccountId == accountId;
646 7 });
647 };
648
2/2
✓ Branch 162 → 143 taken 7 times.
✓ Branch 162 → 163 taken 5 times.
12 for (const auto &config : local.caldavConfigs) {
649
1/1
✓ Branch 144 → 145 taken 7 times.
7 const auto other = configFor(remote.caldavConfigs, config.carddavAccountId);
650
1/2
✗ Branch 147 → 148 not taken.
✓ Branch 147 → 149 taken 7 times.
7 if (other == remote.caldavConfigs.cend())
651 ✗ return true;
652
3/5
✓ Branch 150 → 151 taken 7 times.
✓ Branch 151 → 152 taken 7 times.
✗ Branch 151 → 155 not taken.
✗ Branch 157 → 158 not taken.
✓ Branch 157 → 159 taken 7 times.
14 if (config.selectedCalendars != other->selectedCalendars ||
653
2/3
✓ Branch 153 → 154 taken 7 times.
✗ Branch 154 → 155 not taken.
✓ Branch 154 → 156 taken 7 times.
7 config.readOnlyCalendars != other->readOnlyCalendars)
654 ✗ return true;
655 }
656 5 return false;
657 9 }
658
2/2
✓ Branch 173 → 174 taken 2 times.
✓ Branch 173 → 198 taken 9 times.
11 if (category == QStringLiteral("identities")) {
659 // Only addresses both installations have: applyRemote never adds one.
660
4/4
✓ Branch 174 → 175 taken 2 times.
✓ Branch 194 → 195 taken 3 times.
✓ Branch 196 → 176 taken 2 times.
✓ Branch 196 → 197 taken 1 time.
3 for (auto it = local.identities.constBegin(); it != local.identities.constEnd(); ++it)
661
11/18
✓ Branch 177 → 178 taken 2 times.
✓ Branch 178 → 179 taken 2 times.
✗ Branch 178 → 186 not taken.
✓ Branch 180 → 181 taken 2 times.
✓ Branch 182 → 183 taken 2 times.
✓ Branch 183 → 184 taken 2 times.
✓ Branch 184 → 185 taken 1 time.
✓ Branch 184 → 186 taken 1 time.
✓ Branch 187 → 188 taken 2 times.
✗ Branch 187 → 189 not taken.
✓ Branch 189 → 190 taken 2 times.
✗ Branch 189 → 191 not taken.
✓ Branch 191 → 192 taken 1 time.
✓ Branch 191 → 193 taken 1 time.
✗ Branch 234 → 235 not taken.
✗ Branch 234 → 236 not taken.
✗ Branch 238 → 239 not taken.
✗ Branch 238 → 240 not taken.
2 if (remote.identities.contains(it.key()) && remote.identities.value(it.key()) != it.value())
662 1 return true;
663 1 return false;
664 }
665
2/2
✓ Branch 204 → 205 taken 1 time.
✓ Branch 204 → 207 taken 8 times.
9 if (category == QStringLiteral("templates"))
666 1 return local.templates != remote.templates;
667
2/2
✓ Branch 213 → 214 taken 7 times.
✓ Branch 213 → 221 taken 1 time.
8 if (category == QStringLiteral("general")) {
668 // S106-205 (the F-73 family): only what applyRemote adopts — external
669 // content and the sync intervals stay local, so a difference there could
670 // never be cleared by either resolution.
671
2/2
✓ Branch 215 → 216 taken 6 times.
✓ Branch 215 → 218 taken 1 time.
13 return local.general.defaultView != remote.general.defaultView ||
672
2/2
✓ Branch 217 → 218 taken 2 times.
✓ Branch 217 → 219 taken 4 times.
13 local.general.language != remote.general.language;
673 }
674 1 return false;
675 }
676