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: 88.0% 397 / 0 / 451
Functions: 85.7% 48 / 0 / 56
Branches: 65.8% 529 / 0 / 804

quick/QuickMailHost.cpp
Line Branch Exec Source
1 #include "QuickMailHost.h"
2 #include "QuickNetworkDenial.h"
3 #include "app/NamedCommandCatalog.h"
4 #include "CommandBarSources.h"
5 #include "ReminderNotifications.h"
6 #include "app/core/ProcessTimeZone.h"
7 #include "app/core/ReminderScheduler.h"
8 #include "app/core/CalendarSyncCoordinator.h"
9 #include "app/core/ContactSyncCoordinator.h"
10 #include "presentation/ConflictPhotoProvider.h"
11 #include "presentation/ComposeThumbnailProvider.h"
12 #include "presentation/ContactPhotoProvider.h"
13 #include "presentation/MapTileProvider.h"
14 #include "data/CalendarStore.h"
15 #include "DesktopActivation.h"
16 #include "app/core/MailSessionCore.h"
17 #include "app/core/AccountSession.h"
18 #include "app/MailtoUrl.h"
19 #include "controller/MailController.h"
20 #include "data/MailCache.h"
21 #include "performance/MailOpenPerformanceTracker.h"
22 #include "performance/ThreadSelectionPerformanceTracker.h"
23 #include <QPointer>
24 #include <QDir>
25 #include <QFont>
26 #include <QFontDatabase>
27 #include <QGuiApplication>
28 #include <QTimer>
29 #include <QFileInfo>
30 #include <QQmlContext>
31 #include <QDesktopServices>
32
33 795 QuickMailHost::QuickMailHost(MailSessionCore &session, QString profileDirectory, QObject *parent,
34 795 MailSearchReadService *searchReader)
35 795 : QObject(parent), m_session(session), m_profileDirectory(std::move(profileDirectory)),
36
5/5
✓ Branch 5 → 6 taken 795 times.
✓ Branch 6 → 7 taken 795 times.
✓ Branch 10 → 11 taken 795 times.
✓ Branch 11 → 12 taken 795 times.
✓ Branch 12 → 13 taken 795 times.
1590 m_workspace(this), m_tokens(this), m_reader(this), m_htmlReader(m_reader, session, m_tokens, m_workspace, this), m_commands(this),
37
9/9
✓ Branch 7 → 8 taken 795 times.
✓ Branch 8 → 9 taken 795 times.
✓ Branch 9 → 10 taken 795 times.
✓ Branch 14 → 15 taken 795 times.
✓ Branch 17 → 18 taken 795 times.
✓ Branch 18 → 19 taken 795 times.
✓ Branch 20 → 21 taken 795 times.
✓ Branch 27 → 28 taken 795 times.
✓ Branch 39 → 40 taken 795 times.
2385 m_backend(session, m_workspace, m_profileDirectory, this, searchReader), m_engine(this) {
38
1/1
✓ Branch 40 → 41 taken 795 times.
795 QuickNetworkDenial::install(&m_engine);
39
1/1
✓ Branch 41 → 42 taken 795 times.
795 Q_INIT_RESOURCE(precision_ui);
40
1/1
✓ Branch 42 → 43 taken 795 times.
795 Q_INIT_RESOURCE(quick_html_ui);
41
1/1
✓ Branch 43 → 44 taken 795 times.
795 m_commands.bindWorkspace(&m_workspace);
42 // S105.8.2: menu entries name their command; the key shown is the one
43 // that runs it here.
44 85 const auto menuKey = [this](const QString &command) {
45
2/2
✓ Branch 2 → 3 taken 85 times.
✓ Branch 3 → 4 taken 85 times.
85 return m_commands.effectiveKey(command).toString(QKeySequence::PortableText);
46 795 };
47
2/2
✓ Branch 54 → 50 taken 3180 times.
✓ Branch 54 → 55 taken 795 times.
3975 for (QuickMailMenu *menu : {m_backend.mailMenu(), m_backend.appMenu(), m_backend.contextMenu(), m_backend.searchMenu()})
48 3180 menu->setKeyResolver(menuKey);
49 1867 const auto updateTrayPalette = [this] {
50
4/4
✓ Branch 2 → 3 taken 1867 times.
✓ Branch 7 → 8 taken 1867 times.
✓ Branch 8 → 9 taken 1867 times.
✓ Branch 10 → 11 taken 1867 times.
3734 m_tray.setBadgeColor(QColor(m_tokens.palette().value(QStringLiteral("danger")).toString()));
51 1867 };
52
1/1
✓ Branch 55 → 56 taken 795 times.
795 connect(&m_tokens, &DesignTokens::changed, this, updateTrayPalette);
53
1/1
✓ Branch 57 → 58 taken 795 times.
795 updateTrayPalette();
54
1/1
✓ Branch 58 → 59 taken 795 times.
795 connect(&m_htmlReader, &QuickHtmlReader::externalLinkRequested, this, [](const QUrl &url) { QDesktopServices::openUrl(url); });
55
1/1
✓ Branch 60 → 61 taken 795 times.
795 connect(&m_workspace, &WorkspaceState::readingModeChanged, this, [this] {
56
1/1
✓ Branch 3 → 4 taken 59 times.
59 m_reader.setReadingMode(m_workspace.readingMode());
57 59 });
58
1/1
✓ Branch 63 → 64 taken 795 times.
795 m_reader.setReadingMode(m_workspace.readingMode());
59
1/1
✓ Branch 65 → 66 taken 795 times.
795 connect(&m_reader, &MailReaderState::changed, this, [this] {
60 // S93.5.2: the invitation card follows the displayed mail.
61
4/4
✓ Branch 4 → 5 taken 6838 times.
✓ Branch 5 → 6 taken 6838 times.
✓ Branch 6 → 7 taken 6838 times.
✓ Branch 7 → 8 taken 6838 times.
6838 m_backend.invitation()->setMail(WorkspaceState::decode(m_reader.identity()), m_reader.attachments(), m_reader.sender());
62
2/2
✓ Branch 15 → 16 taken 784 times.
✓ Branch 15 → 24 taken 6054 times.
6838 if (m_reader.status() == "ready")
63
1/1
✓ Branch 20 → 21 taken 784 times.
1568 MailJD::Performance::MailOpenPerformanceTracker::instance().quickDocumentCommitted(
64
2/2
✓ Branch 18 → 19 taken 784 times.
✓ Branch 19 → 20 taken 784 times.
1568 WorkspaceState::decode(m_reader.identity()), m_reader.renderRevision());
65 6838 });
66
1/1
✓ Branch 67 → 68 taken 795 times.
795 connect(&m_engine, &QQmlEngine::warnings, this, [this](const QList<QQmlError> &warnings) {
67
4/4
✓ Branch 5 → 6 taken 30 times.
✓ Branch 6 → 7 taken 30 times.
✓ Branch 10 → 4 taken 30 times.
✓ Branch 10 → 11 taken 30 times.
60 for (const auto &warning : warnings) m_warnings.append(warning.toString());
68 30 });
69 // ─── Q16 + F-36 (SPRINT-104): new-mail notification chain ───
70 // Header batches of the folder the user reads feed the batcher (any
71 // folder now, not just INBOX); it clusters bursts and buffers until the
72 // first INBOX sync completed. The F-36 guards keep the individual chain
73 // to genuinely new, unseen mail: only folders shown once (their
74 // pagination is below the watermark), only uids above the watermark
75 // established at presentation, and never \Seen mail (the audit: mail
76 // read on another device must not announce). Arrivals counted here stay
77 // silent when the unread rise was already summarized while the folder
78 // was closed.
79
1/1
✓ Branch 70 → 71 taken 795 times.
795 connect(m_session.controller(), &MailController::headersStored, this,
80 795 [this](const QString &folderPath, qint64 folderId, quint32,
81 const QList<MailHeader> &headers) {
82 Q_UNUSED(folderPath);
83 ✗ considerStoredHeadersForNotifications(folderId, headers);
84 ✗ });
85 // F-36: a folder arms its individual chain when its authoritative
86 // presentation completed — after the catch-up sync, so mail that arrived
87 // before this session (still uncached) is below the arming watermark and
88 // never announces. The watermark starts at the cache's highest stored
89 // uid, so backfilled older pages stay silent and only later arrivals
90 // notify. Offline opens arm on the re-sync after a reconnect.
91
1/1
✓ Branch 73 → 74 taken 795 times.
795 connect(m_session.controller(), &MailController::authoritativeFolderPresented, this,
92 795 [this](qint64 folderId, int, quint64) { armFolderNotifications(folderId); });
93
1/1
✓ Branch 76 → 77 taken 795 times.
795 connect(m_session.controller(), &MailController::inboxFirstSyncCompleted, this,
94 795 [this](bool initialLoad) { completeInboxFirstSync(initialLoad); });
95 // F-36 (SPRINT-104, D-12): unread rises of folders the user is not
96 // reading arrive as one folder summary — the badge source (STATUS
97 // polling / NOTIFY) is the only thing that sees them. The summary's
98 // "open" reveals the folder; the counted mails stay silent when the
99 // folder is opened later (the individual chain consumes the summary
100 // count first). Since F-50 the background accounts feed the same chain
101 // through their own STATUS watches; their summaries name the account.
102
1/1
✓ Branch 79 → 80 taken 795 times.
795 connect(m_session.controller(), &MailController::unreadCountChanged, this,
103 795 [this](const QString &folderPath, int unread) {
104
1/1
✓ Branch 4 → 5 taken 92 times.
92 m_arrivals.onUnreadCount(m_session.controller()->accountId(), folderPath, unread);
105 92 });
106
1/2
✓ Branch 82 → 83 taken 795 times.
✗ Branch 82 → 86 not taken.
795 if (auto *background = m_session.backgroundAccounts()) {
107 795 connect(background, &BackgroundAccountSession::backgroundUnreadChanged, this,
108
1/1
✓ Branch 83 → 84 taken 795 times.
1590 [this](const QString &accountId, const QString &accountName,
109 const QString &folderPath, int unread) {
110 ✗ m_arrivalAccountIds.insert(accountName, accountId);
111 ✗ m_arrivals.onUnreadCount(accountName, folderPath, unread);
112 ✗ });
113 }
114 795 connect(&m_arrivals, &FolderArrivalWatcher::arrivalsDetected, this,
115
1/1
✓ Branch 86 → 87 taken 795 times.
795 [this](const QString &account, const QString &folderPath, int count) {
116
4/7
✓ Branch 3 → 4 taken 9 times.
✓ Branch 4 → 5 taken 9 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 9 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 9 times.
9 if (!m_backend.preferences()->notifications() || count <= 0) return;
117 // The cache keys folders by the account name they arrived with
118 // — also for a background account (F-50).
119 9 const bool selectedAccount = account == m_session.controller()->accountId();
120
1/1
✓ Branch 15 → 16 taken 9 times.
9 const qint64 folderId = m_session.cache()->ensureFolder(account, folderPath);
121
1/1
✓ Branch 17 → 18 taken 9 times.
9 const QString name = m_backend.folderActions()->folderLeafName(folderPath);
122 const QString body = selectedAccount
123
1/3
✓ Branch 19 → 20 taken 8 times.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
16 ? tr("%n new message(s) in %1", "", count).arg(name)
124
9/11
✓ Branch 18 → 19 taken 8 times.
✓ Branch 18 → 22 taken 1 time.
✓ Branch 21 → 24 taken 8 times.
✓ Branch 22 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 8 times.
✓ Branch 26 → 27 taken 8 times.
✓ Branch 26 → 28 taken 1 time.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
17 : tr("%n new message(s) in %1 (%2)", "", count).arg(name, account);
125 9 uint id = 0;
126
2/5
✓ Branch 28 → 29 taken 9 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 34 taken 9 times.
✗ Branch 30 → 31 not taken.
✗ Branch 31 → 32 not taken.
9 if (m_notifier.isAvailable()) id = m_notifier.notifySummary(tr("New mail"), body);
127
2/3
✓ Branch 34 → 35 taken 9 times.
✗ Branch 34 → 37 not taken.
✓ Branch 35 → 36 taken 9 times.
9 if (id == 0) id = localNotificationId();
128
2/2
✓ Branch 37 → 38 taken 9 times.
✓ Branch 38 → 39 taken 9 times.
9 m_notificationTargets.insert(id, qMakePair(folderId, qint64(-1)));
129
3/3
✓ Branch 39 → 40 taken 8 times.
✓ Branch 39 → 41 taken 1 time.
✓ Branch 42 → 43 taken 9 times.
9 m_folderRevealAccounts.insert(id, selectedAccount ? QString() : account);
130
1/1
✓ Branch 45 → 46 taken 9 times.
9 m_summarizedArrivals[folderId] = m_summarizedArrivals.value(folderId, 0) + count;
131 18 m_lastNotification = {{QStringLiteral("id"), id},
132
1/1
✓ Branch 53 → 54 taken 9 times.
18 {QStringLiteral("summary"), tr("New mail")},
133 18 {QStringLiteral("folder"), folderPath},
134 18 {QStringLiteral("account"), account},
135
2/4
✓ Branch 71 → 72 taken 45 times.
✓ Branch 71 → 73 taken 9 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
63 {QStringLiteral("count"), count}};
136
1/5
✓ Branch 67 → 68 taken 9 times.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 148 not taken.
✗ Branch 146 → 147 not taken.
✗ Branch 146 → 148 not taken.
63 });
137 795 connect(&m_batcher, &NotificationBatcher::notifyIndividual, this,
138
1/1
✓ Branch 88 → 89 taken 795 times.
795 [this](const QString &from, const QString &subject, qint64 uid, qint64 folderId) {
139
2/3
✓ Branch 3 → 4 taken 5 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 5 times.
5 if (!m_backend.preferences()->notifications()) return;
140 // Without a notification service (headless/test) the chain
141 // still registers a local id: the action wiring stays
142 // observable, only the popup is absent.
143 5 uint id = 0;
144
2/4
✓ Branch 6 → 7 taken 5 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 12 taken 5 times.
✗ Branch 9 → 10 not taken.
5 if (m_notifier.isAvailable()) id = m_notifier.notify(from, subject);
145
2/3
✓ Branch 12 → 13 taken 5 times.
✗ Branch 12 → 15 not taken.
✓ Branch 13 → 14 taken 5 times.
5 if (id == 0) id = localNotificationId();
146
2/2
✓ Branch 15 → 16 taken 5 times.
✓ Branch 16 → 17 taken 5 times.
5 m_notificationTargets.insert(id, qMakePair(folderId, uid));
147 10 m_lastNotification = {{QStringLiteral("id"), id},
148 10 {QStringLiteral("from"), from},
149
2/4
✓ Branch 33 → 34 taken 15 times.
✓ Branch 33 → 35 taken 5 times.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
30 {QStringLiteral("subject"), subject}};
150
1/5
✓ Branch 29 → 30 taken 5 times.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 74 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
20 });
151 795 connect(&m_batcher, &NotificationBatcher::notifySummary, this,
152
1/1
✓ Branch 90 → 91 taken 795 times.
795 [this](const QString &title, const QString &body, int count,
153 qint64 folderId, qint64 newestUid) {
154
2/3
✓ Branch 3 → 4 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 if (!m_backend.preferences()->notifications()) return;
155 1 uint id = 0;
156
2/4
✓ Branch 6 → 7 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 1 time.
✗ Branch 8 → 9 not taken.
1 if (m_notifier.isAvailable()) id = m_notifier.notifySummary(title, body);
157
2/3
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 13 not taken.
✓ Branch 11 → 12 taken 1 time.
1 if (id == 0) id = localNotificationId();
158 // The summary's "open" reveals the newest mail of the burst.
159
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
1 m_notificationTargets.insert(id, qMakePair(folderId, newestUid));
160 2 m_lastNotification = {{QStringLiteral("id"), id},
161 2 {QStringLiteral("summary"), title},
162
2/4
✓ Branch 31 → 32 taken 3 times.
✓ Branch 31 → 33 taken 1 time.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
6 {QStringLiteral("count"), count}};
163
1/5
✓ Branch 27 → 28 taken 1 time.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 69 not taken.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 69 not taken.
4 });
164 // S106-358/360: a closed (dismissed, timed-out) notification leaves no
165 // entry behind — neither here nor in the reminders' maps.
166
1/1
✓ Branch 92 → 93 taken 795 times.
795 connect(&m_notifier, &DesktopNotifier::notificationClosed, this, [this](uint id) {
167 ✗ m_notificationTargets.remove(id);
168 ✗ m_folderRevealAccounts.remove(id);
169 ✗ if (m_reminders) m_reminders->forget(id);
170 ✗ });
171 795 connect(&m_notifier, &DesktopNotifier::actionInvoked, this,
172
1/1
✓ Branch 94 → 95 taken 795 times.
795 &QuickMailHost::handleNotificationAction);
173 795 }
174
175 9 void QuickMailHost::enqueueNotification(const QString &from, const QString &subject,
176 qint64 uid, qint64 folderId) {
177
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 8 times.
9 if (!m_backend.preferences()->notifications()) return;
178 8 m_batcher.addPending(from, subject, uid, folderId);
179 }
180
181 51 bool QuickMailHost::arrivalIsReading(const QString &account, const QString &folder) const {
182
7/8
✓ Branch 5 → 6 taken 50 times.
✓ Branch 5 → 10 taken 1 time.
✓ Branch 8 → 9 taken 36 times.
✓ Branch 8 → 10 taken 14 times.
✓ Branch 12 → 13 taken 50 times.
✓ Branch 12 → 14 taken 1 time.
✓ Branch 14 → 15 taken 51 times.
✗ Branch 14 → 16 not taken.
51 return account == m_session.controller()->accountId() && folder == m_backend.folderPath();
183 }
184
185 7 void QuickMailHost::completeInboxFirstSync(bool initialLoad) {
186 7 m_batcher.setSyncComplete(initialLoad);
187 7 }
188
189 // F-36 (SPRINT-104): see QuickMailHost.h. The two methods are the
190 // production handlers of authoritativeFolderPresented and headersStored.
191 1 void QuickMailHost::armFolderNotifications(qint64 folderId) {
192
1/1
✓ Branch 2 → 3 taken 1 time.
1 m_notifyingFolders.insert(folderId);
193
1/1
✓ Branch 4 → 5 taken 1 time.
1 const qint64 known = m_session.cache()->maxUid(folderId);
194
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
1 if (known > m_folderNotifyWatermark.value(folderId, 0))
195
1/1
✓ Branch 7 → 8 taken 1 time.
1 m_folderNotifyWatermark.insert(folderId, known);
196 1 }
197
198 4 void QuickMailHost::considerStoredHeadersForNotifications(qint64 folderId,
199 const QList<MailHeader> &headers) {
200
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 4 times.
5 if (m_session.controller()->initialInboxLoad()) return;
201
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 3 times.
4 if (!m_notifyingFolders.contains(folderId)) return;
202 3 const qint64 previousWatermark = m_folderNotifyWatermark.value(folderId, 0);
203 3 qint64 watermark = previousWatermark;
204 3 int summarized = m_summarizedArrivals.value(folderId, 0);
205
2/2
✓ Branch 25 → 13 taken 5 times.
✓ Branch 25 → 26 taken 3 times.
8 for (const auto &header : headers) {
206 5 watermark = qMax(watermark, header.uid);
207
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 4 times.
5 if (header.uid <= previousWatermark) continue; // backfill, not new
208
2/2
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 3 times.
4 if (header.isSeen()) continue; // read elsewhere already
209
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 2 times.
3 if (summarized > 0) { // summarized while closed
210 1 --summarized;
211 1 continue;
212 }
213
1/1
✓ Branch 22 → 23 taken 2 times.
2 enqueueNotification(header.from, header.subject, header.uid, folderId);
214 }
215
1/1
✓ Branch 26 → 27 taken 3 times.
3 m_folderNotifyWatermark.insert(folderId, watermark);
216
1/1
✓ Branch 27 → 28 taken 3 times.
3 m_summarizedArrivals.insert(folderId, summarized);
217 }
218
219 // Without a notification service no close event ever arrives: the ids of
220 // the last LocalNotificationIds local notifications stay resolvable, older
221 // ones go (S106-358).
222 15 uint QuickMailHost::localNotificationId() {
223 15 const uint id = --m_localNotificationIds;
224 15 m_localIds.append(id);
225
1/2
✗ Branch 9 → 4 not taken.
✓ Branch 9 → 10 taken 15 times.
15 while (m_localIds.size() > LocalNotificationIds) {
226 ✗ const uint oldest = m_localIds.takeFirst();
227 ✗ m_notificationTargets.remove(oldest);
228 ✗ m_folderRevealAccounts.remove(oldest);
229 }
230 15 return id;
231 }
232
233 5 void QuickMailHost::setNotificationDebounceMs(int ms) {
234 5 m_batcher.setDebounceMs(ms);
235 5 }
236
237 7 void QuickMailHost::handleNotificationAction(uint id, const QString &action,
238 const QString &activationToken) {
239
4/7
✓ Branch 3 → 4 taken 7 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 7 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 7 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 21 taken 7 times.
7 if (m_reminders && m_reminders->handleAction(id, action)) {
240 ✗ if (action == QLatin1String("open") && window())
241 ✗ DesktopActivation::activateWindow(window(), activationToken);
242 3 return;
243 }
244
1/1
✓ Branch 21 → 22 taken 7 times.
7 const auto target = m_notificationTargets.value(id, qMakePair(qint64(-1), qint64(-1)));
245
1/1
✓ Branch 23 → 24 taken 7 times.
7 m_notificationTargets.remove(id);
246
1/1
✓ Branch 24 → 25 taken 7 times.
7 const QString revealAccount = m_folderRevealAccounts.take(id); // S106-358: taken on every action
247 7 const qint64 folderId = target.first;
248 7 const qint64 uid = target.second;
249 // F-36 (SPRINT-104): a folder summary (uid -1) reveals the folder; the
250 // newest mail is unknown without fetching headers, and the folder is
251 // what the popup names.
252
11/12
✓ Branch 25 → 26 taken 3 times.
✓ Branch 25 → 33 taken 4 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 33 taken 1 time.
✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 33 not taken.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 36 taken 5 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 5 times.
✓ Branch 38 → 39 taken 2 times.
✓ Branch 38 → 99 taken 5 times.
9 if (uid < 0 && folderId > 0 && action == QStringLiteral("open")) {
253 // F-50: a summary of a background account switches there first. The
254 // account id resolves from the loaded configurations by name, so the
255 // reveal works whichever path fed the arrival watcher.
256
7/8
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 46 taken 1 time.
✓ Branch 44 → 45 taken 1 time.
✗ Branch 44 → 46 not taken.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 76 taken 1 time.
2 if (!revealAccount.isEmpty() && revealAccount != m_session.controller()->accountId()) {
257 1 QString accountId = m_arrivalAccountIds.value(revealAccount);
258
3/6
✓ Branch 52 → 53 taken 1 time.
✗ Branch 52 → 56 not taken.
✓ Branch 54 → 55 taken 1 time.
✗ Branch 54 → 56 not taken.
✓ Branch 57 → 58 taken 1 time.
✗ Branch 57 → 71 not taken.
1 if (accountId.isEmpty() && m_session.accounts()) {
259
1/2
✓ Branch 69 → 62 taken 2 times.
✗ Branch 69 → 70 not taken.
2 for (const auto &config : m_session.accounts()->configurations()) {
260
2/2
✓ Branch 64 → 65 taken 1 time.
✓ Branch 64 → 67 taken 1 time.
2 if (config.name == revealAccount) {
261 1 accountId = config.id;
262 1 break;
263 }
264 }
265 }
266
2/3
✓ Branch 72 → 73 taken 1 time.
✗ Branch 72 → 74 not taken.
✓ Branch 73 → 74 taken 1 time.
1 if (!accountId.isEmpty()) m_backend.selectAccount(accountId);
267 1 }
268
1/1
✓ Branch 77 → 78 taken 2 times.
2 const QString path = m_session.cache()->folderPath(folderId);
269
7/9
✓ Branch 79 → 80 taken 2 times.
✗ Branch 79 → 84 not taken.
✓ Branch 82 → 83 taken 1 time.
✓ Branch 82 → 84 taken 1 time.
✓ Branch 85 → 86 taken 2 times.
✗ Branch 85 → 87 not taken.
✓ Branch 87 → 88 taken 1 time.
✓ Branch 87 → 89 taken 1 time.
✓ Branch 88 → 89 taken 1 time.
2 if (!path.isEmpty() && m_backend.folderPath() != path) m_backend.selectFolder(path);
270
1/1
✓ Branch 92 → 93 taken 2 times.
4 m_workspace.setArea(QStringLiteral("mail"));
271
2/2
✓ Branch 95 → 96 taken 2 times.
✓ Branch 96 → 97 taken 2 times.
2 DesktopActivation::activateWindow(window(), activationToken);
272 2 return;
273 2 }
274
2/2
✓ Branch 99 → 100 taken 1 time.
✓ Branch 99 → 101 taken 4 times.
5 if (uid <= 0) return;
275
2/2
✓ Branch 107 → 108 taken 3 times.
✓ Branch 107 → 139 taken 1 time.
4 if (action == QStringLiteral("open")) {
276 // Single reveal path: switch to mail, select the notified folder if it
277 // is not current, then publish the identity — the reader follows the
278 // workspace selection like a restore.
279
1/2
✓ Branch 108 → 109 taken 3 times.
✗ Branch 108 → 124 not taken.
3 if (folderId > 0) {
280
1/1
✓ Branch 110 → 111 taken 3 times.
3 const QString path = m_session.cache()->folderPath(folderId);
281
7/9
✓ Branch 112 → 113 taken 3 times.
✗ Branch 112 → 117 not taken.
✓ Branch 115 → 116 taken 1 time.
✓ Branch 115 → 117 taken 2 times.
✓ Branch 118 → 119 taken 3 times.
✗ Branch 118 → 120 not taken.
✓ Branch 120 → 121 taken 1 time.
✓ Branch 120 → 122 taken 2 times.
✓ Branch 121 → 122 taken 1 time.
3 if (!path.isEmpty() && m_backend.folderPath() != path) m_backend.selectFolder(path);
282 3 }
283
1/1
✓ Branch 127 → 128 taken 3 times.
6 m_workspace.setArea(QStringLiteral("mail"));
284 3 MailId identity;
285
1/1
✓ Branch 130 → 131 taken 3 times.
3 identity.accountId = m_backend.accountName();
286 3 identity.folderId = folderId;
287 3 identity.uid = uid;
288
1/1
✓ Branch 133 → 134 taken 3 times.
3 m_workspace.select(identity);
289
1/1
✓ Branch 134 → 135 taken 3 times.
3 m_backend.restoreWorkspaceSelection();
290 // Q16: the compositor focus request carries the notification's
291 // xdg-activation token; without one (X11, tokenless platforms) this
292 // falls back to the plain platform request.
293
2/2
✓ Branch 135 → 136 taken 3 times.
✓ Branch 136 → 137 taken 3 times.
3 DesktopActivation::activateWindow(window(), activationToken);
294
5/10
✓ Branch 143 → 144 taken 1 time.
✗ Branch 143 → 146 not taken.
✓ Branch 144 → 145 taken 1 time.
✗ Branch 144 → 146 not taken.
✓ Branch 147 → 148 taken 1 time.
✗ Branch 147 → 149 not taken.
✓ Branch 149 → 150 taken 1 time.
✗ Branch 149 → 151 not taken.
✓ Branch 151 → 152 taken 1 time.
✗ Branch 151 → 156 not taken.
5 } else if (action == QStringLiteral("mark-read") && folderId > 0) {
295
1/1
✓ Branch 154 → 155 taken 1 time.
2 m_session.controller()->markMailAsSeenInFolder(uid, folderId);
296 }
297 7 }
298
299 1220 QuickMailHost::~QuickMailHost() {
300 // Members are destroyed in reverse declaration order: m_persistence goes
301 // before m_backend, whose destructor still cancels a pending indexed page
302 // and emits change signals. Detach every host slot from the backend first
303 // so no host lambda runs against already destroyed members.
304 795 disconnect(&m_backend, nullptr, this, nullptr);
305 1220 }
306
307 // F100-20: labels that put an icon glyph in front of their text ("✎ Edit")
308 // showed a box where the platform's font fallback does not know the icon
309 // font loaded by the app. The icon font becomes the application font's
310 // last fallback family, so such glyphs render in every Text.
311 765 void QuickMailHost::installIconFontFallback() {
312
4/7
✓ Branch 2 → 3 taken 20 times.
✓ Branch 2 → 13 taken 745 times.
✓ Branch 4 → 5 taken 20 times.
✗ Branch 4 → 13 not taken.
✓ Branch 8 → 9 taken 20 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
785 static const int fontId = QFontDatabase::addApplicationFont(QStringLiteral(":/quick/fonts/materialdesignicons-webfont.ttf"));
313
1/1
✓ Branch 13 → 14 taken 765 times.
765 const QStringList icons = QFontDatabase::applicationFontFamilies(fontId);
314
4/7
✓ Branch 15 → 16 taken 765 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 765 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 765 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 765 times.
765 if (icons.isEmpty() || !qobject_cast<QGuiApplication *>(QCoreApplication::instance())) return;
315
1/1
✓ Branch 23 → 24 taken 765 times.
765 QFont font = QGuiApplication::font();
316
1/1
✓ Branch 24 → 25 taken 765 times.
765 QStringList families = font.families();
317
1/4
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 31 taken 765 times.
✗ Branch 27 → 28 not taken.
✗ Branch 28 → 29 not taken.
765 if (families.isEmpty()) families << font.family();
318
2/2
✓ Branch 33 → 34 taken 745 times.
✓ Branch 33 → 35 taken 20 times.
765 if (families.contains(icons.first())) return;
319
1/1
✓ Branch 36 → 37 taken 20 times.
20 families << icons.first();
320
1/1
✓ Branch 37 → 38 taken 20 times.
20 font.setFamilies(families);
321
1/1
✓ Branch 38 → 39 taken 20 times.
20 QGuiApplication::setFont(font);
322 2255 }
323
324 767 bool QuickMailHost::initialize(bool online, const QString &theme, const QString &layout,
325 const QString &legacyIni, bool startMinimized) {
326
2/3
✓ Branch 2 → 3 taken 767 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 767 times.
767 if (window()) return true;
327
1/1
✓ Branch 5 → 6 taken 767 times.
767 m_htmlReader.setEngine(&m_engine);
328
4/6
✓ Branch 8 → 9 taken 766 times.
✓ Branch 8 → 14 taken 1 time.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 32 taken 765 times.
✗ Branch 407 → 408 not taken.
✗ Branch 407 → 409 not taken.
2300 if (m_session.profileDirectory().isEmpty() ||
329
11/18
✓ Branch 10 → 11 taken 766 times.
✓ Branch 11 → 12 taken 766 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 765 times.
✓ Branch 16 → 17 taken 766 times.
✓ Branch 16 → 18 taken 1 time.
✓ Branch 18 → 19 taken 766 times.
✓ Branch 18 → 20 taken 1 time.
✓ Branch 20 → 21 taken 766 times.
✓ Branch 20 → 22 taken 1 time.
✓ Branch 22 → 23 taken 767 times.
✗ Branch 22 → 24 not taken.
✗ Branch 395 → 396 not taken.
✗ Branch 395 → 397 not taken.
✗ Branch 399 → 400 not taken.
✗ Branch 399 → 401 not taken.
✗ Branch 403 → 404 not taken.
✗ Branch 403 → 405 not taken.
1533 QFileInfo(m_profileDirectory).canonicalFilePath() != m_session.profileDirectory()) {
330 2 m_error = QStringLiteral("The Quick frontend requires its session's open profile.");
331 2 return false;
332 }
333
1/1
✓ Branch 34 → 35 taken 765 times.
765 m_reader.setCachePath(m_session.cache()->databasePath());
334
1/1
✓ Branch 37 → 38 taken 765 times.
765 m_session.controller()->setDisplayPort(&m_reader);
335 // P90.8: display reads project exactly the reading mode's variant; keep
336 // the controller's hint in sync with the workspace mode (initial value
337 // after persistence load, then every mode change).
338 {
339 824 auto syncPreferred = [this] {
340
1/1
✓ Branch 4 → 5 taken 824 times.
824 m_session.controller()->setPreferredDisplayContent(m_workspace.readingMode());
341 824 };
342
1/1
✓ Branch 38 → 39 taken 765 times.
765 syncPreferred();
343 765 connect(&m_workspace, &WorkspaceState::readingModeChanged, this,
344
1/1
✓ Branch 39 → 40 taken 765 times.
824 [syncPreferred]() { syncPreferred(); });
345 // P90.8: the reader asks for the missing variant when a mode switch
346 // hits a projected body; the targeted fetch merges into the displayed
347 // body without a new selection generation (mode change ≠ mail change).
348 765 connect(&m_reader, &MailReaderState::reprojectionRequested, this,
349
1/1
✓ Branch 41 → 42 taken 765 times.
765 [this](bool needHtml) {
350 29 const auto selected = m_workspace.mailId();
351
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 29 times.
29 if (!selected.isValid()) return;
352
1/1
✓ Branch 8 → 9 taken 29 times.
29 m_session.controller()->reprojectBodyVariant(selected, needHtml);
353
1/2
✓ Branch 11 → 12 taken 29 times.
✗ Branch 11 → 14 not taken.
29 });
354 }
355
4/4
✓ Branch 44 → 45 taken 765 times.
✓ Branch 45 → 46 taken 765 times.
✓ Branch 46 → 47 taken 765 times.
✓ Branch 47 → 48 taken 765 times.
765 m_session.accounts()->load(QDir(m_profileDirectory).filePath("accounts"), online);
356
1/1
✓ Branch 54 → 55 taken 765 times.
1530 m_persistence = std::make_unique<WorkspacePersistence>(
357
3/3
✓ Branch 51 → 52 taken 765 times.
✓ Branch 52 → 53 taken 765 times.
✓ Branch 53 → 54 taken 765 times.
2295 QDir(m_profileDirectory).filePath("quick-ui"), m_workspace, m_tokens);
358
2/3
✓ Branch 62 → 63 taken 765 times.
✗ Branch 64 → 65 not taken.
✓ Branch 64 → 70 taken 765 times.
765 if (!m_persistence->load(legacyIni, [this](const MailId &id) {
359 9 return m_backend.acceptsIdentity(id);
360 })) {
361 ✗ m_error = m_persistence->error();
362 ✗ return false;
363 }
364
3/3
✓ Branch 71 → 72 taken 77 times.
✓ Branch 71 → 73 taken 688 times.
✓ Branch 72 → 73 taken 77 times.
765 if (!theme.isEmpty()) m_tokens.setTheme(theme);
365
3/3
✓ Branch 74 → 75 taken 69 times.
✓ Branch 74 → 76 taken 696 times.
✓ Branch 75 → 76 taken 69 times.
765 if (!layout.isEmpty()) m_workspace.setLayout(layout);
366 // Thread view is a local UI preference; restore before the scene loads so
367 // the first presented folder already builds the threaded projection.
368
1/1
✓ Branch 78 → 79 taken 765 times.
765 m_backend.setThreaded(m_persistence->threaded());
369
1/1
✓ Branch 79 → 80 taken 765 times.
765 connect(&m_backend, &QuickMailBackend::threadedChanged, this, [this] {
370 340 m_persistence->setThreaded(m_backend.threaded());
371 340 });
372 // F95-15: the suggestion column (Ctrl+S) is restored like the thread view.
373
1/1
✓ Branch 83 → 84 taken 765 times.
765 m_backend.setSuggestionsVisible(m_persistence->suggestions());
374
1/1
✓ Branch 84 → 85 taken 765 times.
765 connect(&m_backend, &QuickMailBackend::suggestionsVisibleChanged, this, [this] {
375 23 m_persistence->setSuggestions(m_backend.suggestionsVisible());
376 23 });
377 // Sprint 101 (D-26, D-30): the search timeline band and its first-use hint.
378 {
379 765 const QVariantMap search = m_persistence->searchState();
380
3/3
✓ Branch 93 → 94 taken 765 times.
✓ Branch 94 → 95 taken 765 times.
✓ Branch 95 → 96 taken 765 times.
1530 m_backend.searchTabs()->setTimelineShown(!search.value(QStringLiteral("timelineCollapsed")).toBool());
381
3/3
✓ Branch 105 → 106 taken 765 times.
✓ Branch 106 → 107 taken 765 times.
✓ Branch 107 → 108 taken 765 times.
1530 m_backend.searchTabs()->setTimelineHintSeen(search.value(QStringLiteral("timelineHintSeen")).toBool());
382
3/3
✓ Branch 117 → 118 taken 765 times.
✓ Branch 118 → 119 taken 765 times.
✓ Branch 119 → 120 taken 765 times.
1530 m_backend.searchTabs()->setGrouped(search.value(QStringLiteral("grouped")).toBool());
383 13 const auto saveSearch = [this] {
384
3/5
✓ Branch 22 → 23 taken 13 times.
✓ Branch 25 → 26 taken 39 times.
✓ Branch 25 → 27 taken 13 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
91 m_persistence->setSearchState({{QStringLiteral("timelineCollapsed"), !m_backend.searchTabs()->timelineShown()},
385 26 {QStringLiteral("timelineHintSeen"), m_backend.searchTabs()->timelineHintSeen()},
386 26 {QStringLiteral("grouped"), m_backend.searchTabs()->grouped()}});
387
1/5
✓ Branch 21 → 22 taken 13 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 65 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
65 };
388
1/1
✓ Branch 125 → 126 taken 765 times.
765 connect(m_backend.searchTabs(), &MailSearchSessions::timelineShownChanged, this, saveSearch);
389
1/1
✓ Branch 128 → 129 taken 765 times.
765 connect(m_backend.searchTabs(), &MailSearchSessions::timelineHintSeenChanged, this, saveSearch);
390
1/1
✓ Branch 131 → 132 taken 765 times.
765 connect(m_backend.searchTabs(), &MailSearchSessions::groupedChanged, this, saveSearch);
391 765 }
392 // F3.2: the compose surface placement is also a local UI preference.
393
1/1
✓ Branch 137 → 138 taken 765 times.
765 m_backend.compose()->setComposeWindowed(m_persistence->composeWindowed());
394
1/1
✓ Branch 139 → 140 taken 765 times.
765 connect(m_backend.compose(), &QuickComposeBackend::composeWindowedChanged, this, [this] {
395 9 m_persistence->setComposeWindowed(m_backend.compose()->composeWindowed());
396 9 });
397 // Sprint 93 S93.3.9: the calendar opens as it was left (view, work
398 // week, zoom, hidden calendars, last used calendar).
399
1/1
✓ Branch 144 → 145 taken 765 times.
765 m_backend.pim()->restoreViewState(m_persistence->calendarState());
400
1/1
✓ Branch 147 → 148 taken 765 times.
765 connect(m_backend.pim(), &QuickPimBackend::viewStateChanged, this, [this] {
401
2/2
✓ Branch 4 → 5 taken 307 times.
✓ Branch 5 → 6 taken 307 times.
307 m_persistence->setCalendarState(m_backend.pim()->viewState());
402 307 });
403 // Sprint 93 S93.5.1: reminders run on the sync coordinator's store;
404 // the first check (with missed reminders) waits for the window.
405
5/9
✓ Branch 150 → 151 taken 765 times.
✗ Branch 150 → 157 not taken.
✓ Branch 152 → 153 taken 765 times.
✗ Branch 152 → 157 not taken.
✓ Branch 154 → 155 taken 765 times.
✓ Branch 155 → 156 taken 765 times.
✗ Branch 155 → 157 not taken.
✓ Branch 158 → 159 taken 765 times.
✗ Branch 158 → 169 not taken.
765 if (auto *sync = m_session.calendarSync(); sync && sync->store() && sync->store()->isOpen()) {
406
1/1
✓ Branch 160 → 161 taken 765 times.
765 m_reminders = std::make_unique<ReminderNotifications>(*sync, m_notifier, *m_backend.pim(), m_workspace);
407
1/1
✓ Branch 164 → 165 taken 765 times.
765 connect(m_reminders.get(), &ReminderNotifications::statusMessage, this,
408 773 [this](const QString &message) { m_backend.showStatus(message); });
409
1/1
✓ Branch 167 → 168 taken 765 times.
765 QTimer::singleShot(3000, m_reminders.get(), &ReminderNotifications::start);
410 }
411 // Sprint 93 G9: a system time zone change re-pins TZ; displayed times and
412 // reminder triggers are computed again.
413
1/1
✓ Branch 169 → 170 taken 765 times.
765 m_zoneWatcher = std::make_unique<ProcessTimeZoneWatcher>();
414
1/1
✓ Branch 173 → 174 taken 765 times.
765 connect(m_zoneWatcher.get(), &ProcessTimeZoneWatcher::changed, this, [this] {
415 ✗ m_backend.pim()->calendarModel().reload();
416 ✗ m_backend.pim()->nextUp()->refresh();
417 ✗ if (m_reminders && m_reminders->scheduler()) m_reminders->scheduler()->recompute();
418 ✗ });
419 // F93-19: the restored session keeps its area (mail, calendar, tasks,
420 // contacts); WorkspaceState falls back to mail for an unknown one.
421
1/1
✓ Branch 175 → 176 taken 765 times.
765 m_backend.restoreWorkspaceSelection();
422 // Sprint 105 rule 2: every registry command has a handler in the product
423 // (checked by scripts/check_command_handlers.py); the registry is the only list.
424 765 QSet<QString> supported;
425
4/4
✓ Branch 177 → 178 taken 765 times.
✓ Branch 181 → 182 taken 156825 times.
✓ Branch 184 → 180 taken 156825 times.
✓ Branch 184 → 185 taken 765 times.
157590 for (const auto &spec : CommandRegistry::specs()) supported.insert(spec.id);
426
1/1
✓ Branch 185 → 186 taken 765 times.
765 m_commands.setSupported(supported);
427 // Sprint 98: every catalog command has a handler (Main.qml executeNamed,
428 // backend.executeNamedCommand); availability is decided per line.
429 765 QSet<QString> named;
430
4/4
✓ Branch 187 → 188 taken 765 times.
✓ Branch 191 → 192 taken 39780 times.
✓ Branch 194 → 190 taken 39780 times.
✓ Branch 194 → 195 taken 765 times.
40545 for (const auto &command : NamedCommandCatalog::specs()) named.insert(command.name);
431
1/1
✓ Branch 195 → 196 taken 765 times.
765 m_commands.setNamedSupported(named);
432 // Sprint 98 S98.1.4–S98.1.6: completion data, the command history and the
433 // recent folders (D-9, D-20), persisted with the other UI preferences.
434
2/2
✓ Branch 196 → 197 taken 765 times.
✓ Branch 197 → 198 taken 765 times.
765 m_commands.setCompletionSources(commandBarSources(m_session, m_backend, m_commands));
435
1/1
✓ Branch 202 → 203 taken 765 times.
765 m_commands.history().restore(m_persistence->commandBarState());
436
1/1
✓ Branch 204 → 205 taken 765 times.
765 connect(&m_commands, &QuickCommandRouter::historyChanged, this, [this] {
437
2/2
✓ Branch 4 → 5 taken 262 times.
✓ Branch 5 → 6 taken 262 times.
262 m_persistence->setCommandBarState(m_commands.history().save());
438 262 });
439
1/1
✓ Branch 206 → 207 taken 765 times.
765 connect(&m_backend, &QuickMailBackend::folderChanged, this, [this] {
440
4/4
✓ Branch 5 → 6 taken 133 times.
✓ Branch 5 → 12 taken 22 times.
✓ Branch 7 → 8 taken 133 times.
✓ Branch 8 → 9 taken 133 times.
155 if (!m_backend.folderPath().isEmpty()) m_commands.recordFolder(m_backend.accountName(), m_backend.folderPath());
441 155 });
442 // Sprint 97 S97.3.4: contact photos for avatars; the engine owns the
443 // provider, changed cards drop their cached images.
444
3/6
✓ Branch 208 → 209 taken 765 times.
✓ Branch 212 → 213 taken 765 times.
✗ Branch 214 → 215 not taken.
✓ Branch 214 → 216 taken 765 times.
✗ Branch 519 → 520 not taken.
✗ Branch 519 → 521 not taken.
765 auto *photos = new ContactPhotoProvider(m_backend.pim()->contactModel().databasePath());
445
1/1
✓ Branch 219 → 220 taken 765 times.
1530 m_engine.addImageProvider(QStringLiteral("contactphoto"), photos);
446 // Sprint 103 S103.4.3: offline world map tiles.
447
4/7
✓ Branch 222 → 223 taken 765 times.
✓ Branch 223 → 224 taken 765 times.
✓ Branch 227 → 228 taken 765 times.
✗ Branch 230 → 231 not taken.
✓ Branch 230 → 232 taken 765 times.
✗ Branch 534 → 535 not taken.
✗ Branch 534 → 536 not taken.
1530 m_engine.addImageProvider(QStringLiteral("maptile"), new MapTileProvider);
448 // Sprint 107 J4: the two sides' photos of a contact conflict.
449
4/7
✓ Branch 232 → 233 taken 765 times.
✓ Branch 234 → 235 taken 765 times.
✓ Branch 238 → 239 taken 765 times.
✗ Branch 241 → 242 not taken.
✓ Branch 241 → 243 taken 765 times.
✗ Branch 543 → 544 not taken.
✗ Branch 543 → 545 not taken.
1530 m_engine.addImageProvider(QStringLiteral("conflictphoto"), new ConflictPhotoProvider(m_backend.resolver()));
450 // Sprint 108 S108.6.1: thumbnails of the files in a draft store.
451
4/7
✓ Branch 243 → 244 taken 765 times.
✓ Branch 244 → 245 taken 765 times.
✓ Branch 248 → 249 taken 765 times.
✗ Branch 251 → 252 not taken.
✓ Branch 251 → 253 taken 765 times.
✗ Branch 552 → 553 not taken.
✗ Branch 552 → 554 not taken.
1530 m_engine.addImageProvider(QStringLiteral("compose-thumbnails"), new ComposeThumbnailProvider);
452
1/2
✓ Branch 254 → 255 taken 765 times.
✗ Branch 254 → 260 not taken.
765 if (auto *contacts = m_session.contactSync())
453 // S106-950: the engine (a member) deletes the provider before this
454 // object's QObject part disconnects; a sync finishing in between found
455 // freed memory — the guard sees the deletion.
456
1/1
✓ Branch 256 → 257 taken 765 times.
765 connect(contacts, &ContactSyncCoordinator::cardsChanged, this,
457
1/1
✓ Branch 255 → 256 taken 765 times.
1530 [photos = QPointer<ContactPhotoProvider>(photos)](const QStringList &keys) {
458
1/2
✓ Branch 3 → 4 taken 42 times.
✗ Branch 3 → 6 not taken.
42 if (photos) photos->invalidate(keys);
459 42 });
460
1/1
✓ Branch 260 → 261 taken 765 times.
765 auto *context = m_engine.rootContext();
461
2/2
✓ Branch 261 → 262 taken 765 times.
✓ Branch 262 → 263 taken 765 times.
765 context->setContextProperty("contextTokens", &m_tokens);
462
2/2
✓ Branch 264 → 265 taken 765 times.
✓ Branch 265 → 266 taken 765 times.
765 context->setContextProperty("contextWorkspace", &m_workspace);
463
2/2
✓ Branch 267 → 268 taken 765 times.
✓ Branch 268 → 269 taken 765 times.
765 context->setContextProperty("contextCommands", &m_commands);
464
2/2
✓ Branch 271 → 272 taken 765 times.
✓ Branch 272 → 273 taken 765 times.
765 context->setContextProperty("contextMailModel", m_backend.mailModel());
465
2/2
✓ Branch 274 → 275 taken 765 times.
✓ Branch 275 → 276 taken 765 times.
765 context->setContextProperty("contextReader", &m_reader);
466
2/2
✓ Branch 277 → 278 taken 765 times.
✓ Branch 278 → 279 taken 765 times.
765 context->setContextProperty("contextBackend", &m_backend);
467
2/2
✓ Branch 282 → 283 taken 765 times.
✓ Branch 283 → 284 taken 765 times.
765 context->setContextProperty("contextPreviewBody", QString{});
468
2/2
✓ Branch 287 → 288 taken 765 times.
✓ Branch 288 → 289 taken 765 times.
765 context->setContextProperty("contextAbout", &m_about);
469 // Sprint 100 SB-11: the settings bind to the tray controller itself.
470
2/2
✓ Branch 290 → 291 taken 765 times.
✓ Branch 291 → 292 taken 765 times.
765 context->setContextProperty("contextTray", &m_tray);
471
2/2
✓ Branch 293 → 294 taken 765 times.
✓ Branch 294 → 295 taken 765 times.
765 context->setContextProperty("contextSettingsCatalog", &m_settingsCatalog);
472
2/2
✓ Branch 296 → 297 taken 765 times.
✓ Branch 297 → 298 taken 765 times.
765 context->setContextProperty("contextSettings", &m_settingsNavigation);
473 1150 m_settingsNavigation.setActiveAccountResolver([this] { return m_backend.activeAccountId(); });
474
2/2
✓ Branch 302 → 303 taken 765 times.
✓ Branch 303 → 304 taken 765 times.
765 MailJD::Performance::ThreadSelectionPerformanceTracker::instance().prepare();
475
3/5
✓ Branch 315 → 316 taken 765 times.
✓ Branch 318 → 319 taken 765 times.
✓ Branch 318 → 320 taken 765 times.
✗ Branch 601 → 602 not taken.
✗ Branch 601 → 603 not taken.
3060 m_engine.setInitialProperties({{QStringLiteral("startMinimized"),
476
5/5
✓ Branch 307 → 308 taken 764 times.
✓ Branch 307 → 311 taken 1 time.
✓ Branch 309 → 310 taken 764 times.
✓ Branch 310 → 311 taken 1 time.
✓ Branch 310 → 312 taken 763 times.
1530 startMinimized || m_backend.preferences()->startMinimized()}});
477
1/1
✓ Branch 322 → 323 taken 765 times.
765 installIconFontFallback();
478
3/3
✓ Branch 323 → 324 taken 765 times.
✓ Branch 324 → 325 taken 765 times.
✓ Branch 325 → 326 taken 765 times.
765 m_engine.load(QUrl("qrc:/quick/Main.qml"));
479
2/3
✓ Branch 328 → 329 taken 765 times.
✗ Branch 329 → 330 not taken.
✓ Branch 329 → 342 taken 765 times.
765 if (!window()) {
480 ✗ m_error = m_warnings.isEmpty() ? QStringLiteral("The Quick application scene could not be loaded.")
481 ✗ : m_warnings.join('\n');
482 ✗ return false;
483 }
484
2/2
✓ Branch 342 → 343 taken 765 times.
✓ Branch 343 → 344 taken 765 times.
765 m_commands.attach(window());
485
3/3
✓ Branch 344 → 345 taken 765 times.
✓ Branch 350 → 351 taken 765 times.
✓ Branch 351 → 352 taken 765 times.
1530 m_listView = window()->findChild<QQuickItem *>(QStringLiteral("mailList"));
486 // beforeSynchronizing runs with the GUI thread blocked. Capture the identity
487 // actually entering this scene frame; afterFrameEnd submits that captured
488 // revision back to the GUI thread and rejects intervening selections/renders.
489
2/2
✓ Branch 354 → 355 taken 765 times.
✓ Branch 355 → 356 taken 765 times.
765 connect(window(), &QQuickWindow::beforeSynchronizing, this, [this] {
490 18382 m_listFrameIdentity = {};
491 18382 m_listFrameGeneration.clear();
492 18382 m_listFrameRow = -1;
493
1/1
✓ Branch 5 → 6 taken 18382 times.
18382 auto &selectionTrace = MailJD::Performance::ThreadSelectionPerformanceTracker::instance();
494 ✗ if (selectionTrace.isActive() && m_listView && m_listView->isVisible() &&
495
4/11
✓ Branch 6 → 7 taken 18382 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 19 taken 18382 times.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 18382 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 70 taken 18382 times.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
18382 m_workspace.area() == "mail" && m_workspace.activeTab() == 0) {
496 ✗ const auto selected = m_workspace.mailId();
497 ✗ const int row = m_backend.loadedRowForIdentity(selected);
498 ✗ if (row >= 0) {
499 ✗ const auto name = QStringLiteral("mailRow-%1").arg(row);
500 ✗ auto *delegate = m_listView->findChild<QQuickItem *>(name);
501 ✗ if (delegate && delegate->isVisible() &&
502 ✗ delegate->property("selectedForAction").toBool()) {
503 ✗ const qreal y = delegate->mapToItem(m_listView, QPointF{}).y();
504 ✗ if (y < m_listView->height() && y + delegate->height() > 0) {
505 ✗ m_listFrameIdentity = selected;
506 ✗ m_listFrameGeneration = m_workspace.generation();
507 ✗ m_listFrameRow = row;
508 }
509 }
510 ✗ }
511 ✗ }
512 18382 m_frameIdentity = {};
513 18382 m_frameRevision = 0;
514
9/10
✓ Branch 74 → 75 taken 9755 times.
✓ Branch 74 → 78 taken 8627 times.
✓ Branch 77 → 78 taken 3892 times.
✓ Branch 77 → 79 taken 5863 times.
✓ Branch 80 → 81 taken 9755 times.
✓ Branch 80 → 82 taken 8627 times.
✓ Branch 82 → 83 taken 18382 times.
✗ Branch 82 → 84 not taken.
✓ Branch 84 → 85 taken 12519 times.
✓ Branch 84 → 86 taken 5863 times.
18391 if (m_reader.status() != "ready" || m_workspace.area() != "mail") return;
515
2/2
✓ Branch 86 → 87 taken 5863 times.
✓ Branch 87 → 88 taken 5863 times.
5863 const auto identity = WorkspaceState::decode(m_reader.identity());
516
2/2
✓ Branch 91 → 92 taken 9 times.
✓ Branch 91 → 93 taken 5854 times.
5863 if (identity != m_workspace.mailId()) return;
517 5854 m_frameIdentity = identity;
518 5854 m_frameRevision = m_reader.renderRevision();
519
2/2
✓ Branch 97 → 98 taken 5854 times.
✓ Branch 97 → 100 taken 9 times.
5863 }, Qt::DirectConnection);
520
2/2
✓ Branch 357 → 358 taken 765 times.
✓ Branch 358 → 359 taken 765 times.
765 connect(window(), &QQuickWindow::afterFrameEnd, this, [this] {
521 18382 const auto listIdentity = m_listFrameIdentity;
522 18382 const auto listGeneration = m_listFrameGeneration;
523 18382 const int listRow = m_listFrameRow;
524
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 18382 times.
18382 if (listIdentity.isValid()) {
525 ✗ QMetaObject::invokeMethod(this, [this, listIdentity, listGeneration, listRow] {
526 ✗ if (!m_listView || !m_listView->isVisible() || m_workspace.area() != "mail" ||
527 ✗ m_workspace.activeTab() != 0 || m_workspace.mailId() != listIdentity ||
528 ✗ m_workspace.generation() != listGeneration ||
529 ✗ m_backend.loadedRowForIdentity(listIdentity) != listRow) return;
530 ✗ const auto name = QStringLiteral("mailRow-%1").arg(listRow);
531 ✗ auto *delegate = m_listView->findChild<QQuickItem *>(name);
532 ✗ if (!delegate || !delegate->isVisible() ||
533 ✗ !delegate->property("selectedForAction").toBool()) return;
534 ✗ MailJD::Performance::ThreadSelectionPerformanceTracker::instance()
535 ✗ .quickFramePresented(listIdentity);
536 ✗ }, Qt::QueuedConnection);
537 }
538 18382 const auto identity = m_frameIdentity;
539 18382 const auto revision = m_frameRevision;
540
5/6
✓ Branch 13 → 14 taken 5854 times.
✓ Branch 13 → 15 taken 12528 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 5854 times.
✓ Branch 17 → 18 taken 12528 times.
✓ Branch 17 → 19 taken 5854 times.
18382 if (!identity.isValid() || !revision) return;
541
1/3
✓ Branch 20 → 21 taken 5854 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
5854 QMetaObject::invokeMethod(this, [this, identity, revision] {
542
5/10
✓ Branch 7 → 8 taken 5705 times.
✓ Branch 7 → 17 taken 3 times.
✓ Branch 25 → 26 taken 5767 times.
✗ Branch 25 → 27 not taken.
✓ Branch 27 → 28 taken 78 times.
✓ Branch 27 → 29 taken 5689 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
11475 if (m_reader.status() != "ready" || m_workspace.area() != "mail" ||
543
5/6
✓ Branch 4 → 5 taken 5708 times.
✓ Branch 4 → 17 taken 59 times.
✓ Branch 10 → 11 taken 5705 times.
✗ Branch 10 → 17 not taken.
✓ Branch 12 → 13 taken 5689 times.
✓ Branch 12 → 17 taken 16 times.
17164 m_workspace.mailId() != identity || m_reader.renderRevision() != revision ||
544
9/14
✓ Branch 13 → 14 taken 5689 times.
✓ Branch 14 → 15 taken 5689 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 5689 times.
✓ Branch 19 → 20 taken 5689 times.
✓ Branch 19 → 21 taken 78 times.
✓ Branch 21 → 22 taken 5689 times.
✓ Branch 21 → 23 taken 78 times.
✓ Branch 23 → 24 taken 5708 times.
✓ Branch 23 → 25 taken 59 times.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
11534 WorkspaceState::decode(m_reader.identity()) != identity) return;
545 5689 MailJD::Performance::MailOpenPerformanceTracker::instance().quickFramePresented(identity, revision);
546
3/3
✓ Branch 31 → 32 taken 5689 times.
✓ Branch 32 → 33 taken 5689 times.
✓ Branch 33 → 34 taken 5689 times.
5689 emit readerFrameSubmitted(WorkspaceState::encode(identity), QString::number(revision));
547 }, Qt::QueuedConnection);
548
6/6
✓ Branch 24 → 25 taken 5854 times.
✓ Branch 24 → 26 taken 12528 times.
✓ Branch 28 → 29 taken 5854 times.
✓ Branch 28 → 30 taken 12528 times.
✓ Branch 32 → 33 taken 5854 times.
✓ Branch 32 → 35 taken 12528 times.
43438 }, Qt::DirectConnection);
549
2/2
✓ Branch 360 → 361 taken 765 times.
✓ Branch 361 → 362 taken 765 times.
765 m_tray.setParentWindow(window());
550 // The desktop badge has always represented this account's INBOX. Folder
551 // navigation and search-result counts must not replace it.
552 793 const auto restoreInboxBadge = [this] {
553
1/1
✓ Branch 2 → 3 taken 793 times.
793 const auto account = m_backend.accountName();
554
1/1
✓ Branch 4 → 5 taken 793 times.
793 const auto badges = m_session.cache()->loadAllBadges(account);
555
1/1
✓ Branch 9 → 10 taken 793 times.
1586 const auto folder = m_session.cache()->findFolderId(account, QStringLiteral("INBOX"));
556
4/4
✓ Branch 15 → 16 taken 793 times.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 22 taken 791 times.
✓ Branch 28 → 29 taken 793 times.
3172 m_tray.setUnreadCount(badges.contains(QStringLiteral("INBOX"))
557
5/9
✓ Branch 20 → 21 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 791 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 33 taken 791 times.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
797 ? badges.value(QStringLiteral("INBOX"))
558
3/3
✓ Branch 22 → 23 taken 589 times.
✓ Branch 22 → 26 taken 202 times.
✓ Branch 24 → 25 taken 589 times.
791 : folder > 0 ? m_session.cache()->unreadCount(folder) : 0);
559 793 };
560
1/1
✓ Branch 363 → 364 taken 765 times.
765 connect(m_session.accounts(), &AccountSession::accountsLoaded, this, restoreInboxBadge);
561
1/1
✓ Branch 365 → 366 taken 765 times.
765 restoreInboxBadge();
562
1/1
✓ Branch 367 → 368 taken 765 times.
765 connect(m_session.controller(), &MailController::cachedUnreadCountChanged, this,
563 770 [this](const QString &folder, int count) {
564
1/2
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 10 not taken.
5 if (folder.compare(QStringLiteral("INBOX"), Qt::CaseInsensitive) == 0)
565 5 m_tray.setUnreadCount(count);
566 5 });
567
1/1
✓ Branch 370 → 371 taken 765 times.
765 connect(m_session.controller(), &MailController::unreadCountChanged, this,
568 857 [this](const QString &folder, int count) {
569
2/2
✓ Branch 8 → 9 taken 68 times.
✓ Branch 8 → 10 taken 24 times.
92 if (folder.compare(QStringLiteral("INBOX"), Qt::CaseInsensitive) == 0)
570 68 m_tray.setUnreadCount(count);
571 92 });
572
1/1
✓ Branch 374 → 375 taken 765 times.
765 connect(&m_tray, &QuickTrayController::checkMailRequested,
573 765 m_session.controller()->flagSync(), &FolderFlagSync::triggerPollNow);
574
1/1
✓ Branch 376 → 377 taken 765 times.
765 connect(&m_tray, &QuickTrayController::inboxRequested, this, [this] {
575
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
1 if (window()) QMetaObject::invokeMethod(window(), "openInbox");
576 1 });
577
1/1
✓ Branch 378 → 379 taken 765 times.
765 connect(&m_tray, &QuickTrayController::quitRequested, this, [this] {
578
2/3
✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (!window()) return;
579 2 QVariant accepted;
580
3/3
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
2 QMetaObject::invokeMethod(window(), "requestQuit", Q_RETURN_ARG(QVariant, accepted));
581
4/4
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
2 if (!accepted.toBool()) m_tray.showWindow();
582 2 });
583
1/1
✓ Branch 380 → 381 taken 765 times.
765 connect(&m_tray, &QuickTrayController::composeRequested, this, [this] {
584 // The tray's "New message" opens the main composer (Ctrl+N's extra
585 // window is a keyboard gesture, F95-17).
586
3/4
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 15 not taken.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 2 times.
4 if (window()) QMetaObject::invokeMethod(window(), "execute",
587 4 Q_ARG(QVariant, QStringLiteral("compose.new")));
588 2 });
589
1/1
✓ Branch 383 → 384 taken 765 times.
765 m_persistence->restoreTray(&m_tray);
590
2/2
✓ Branch 384 → 385 taken 765 times.
✓ Branch 386 → 387 taken 765 times.
765 window()->setProperty("trayController", QVariant::fromValue(&m_tray));
591 // SB-11: Main.qml reads closeToTray from the controller (Runtime.tray).
592
1/1
✓ Branch 388 → 389 taken 765 times.
765 connect(&m_tray, &QuickTrayController::closeToTrayChanged, this, [this] {
593
1/2
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 7 not taken.
8 if (m_persistence) m_persistence->setCloseToTray(m_tray.closeToTray());
594 8 });
595 765 return true;
596 2295 }
597
598 5 bool QuickMailHost::activate(const QString &mailto, bool compose,
599 const QString &activationToken) {
600
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (!window()) return false;
601
4/6
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 9 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 85 not taken.
5 if (compose || !mailto.isEmpty()) {
602
2/2
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 20 taken 4 times.
5 if (mailto.size() > 65536) {
603 1 m_error = QStringLiteral("Invalid or oversized mailto request.");
604 2 return false;
605 }
606
2/4
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 3 times.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
8 const auto request = mailto.isEmpty() ? std::optional<MailtoRequest>(MailtoRequest{})
607
3/3
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
4 : MailtoRequest::parse(mailto);
608
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 35 taken 3 times.
4 if (!request) {
609 1 m_error = QStringLiteral("Invalid or oversized mailto request.");
610 1 return false;
611 }
612 3 const QVariant draft = QVariantMap{{"to", request->to}, {"cc", request->cc},
613 12 {"bcc", request->bcc}, {"subject", request->subject},
614
2/4
✓ Branch 51 → 52 taken 18 times.
✓ Branch 51 → 53 taken 3 times.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 97 not taken.
24 {"body", request->body}, {"inReplyTo", request->inReplyTo}};
615 3 QVariant admitted;
616
3/3
✓ Branch 55 → 56 taken 3 times.
✓ Branch 56 → 57 taken 3 times.
✓ Branch 57 → 58 taken 3 times.
3 if (!QMetaObject::invokeMethod(window(), "requestExternalCompose", Q_RETURN_ARG(QVariant, admitted),
617
4/7
✓ Branch 58 → 59 taken 3 times.
✗ Branch 58 → 61 not taken.
✓ Branch 59 → 60 taken 3 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 3 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 71 taken 3 times.
6 Q_ARG(QVariant, draft)) || !admitted.toBool()) {
618 ✗ m_error = QStringLiteral("The composer is busy. Finish the current action before opening this request.");
619 ✗ return false;
620 }
621 4 }
622 3 m_error.clear();
623 // Second-instance activation: the forwarded token wins; without one the
624 // environment token (KDE startup path) still applies. S106-357: the
625 // request is delivered at this point; a compositor that does not grant
626 // focus (Wayland without a token) is no failure of the request — it was
627 // taken for one, so `mailjd --compose` exited 3 and the forwarding
628 // instance reported a busy composer.
629 3 DesktopActivation::activateWindow(window(), activationToken);
630 3 return true;
631 3 }
632
633 12713 QQuickWindow *QuickMailHost::window() const {
634
3/3
✓ Branch 2 → 3 taken 12713 times.
✓ Branch 4 → 5 taken 768 times.
✓ Branch 4 → 6 taken 11945 times.
25426 return m_engine.rootObjects().isEmpty() ? nullptr
635
5/7
✓ Branch 6 → 7 taken 11945 times.
✓ Branch 7 → 8 taken 11945 times.
✓ Branch 8 → 9 taken 11945 times.
✓ Branch 11 → 12 taken 11945 times.
✓ Branch 11 → 13 taken 768 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
25426 : qobject_cast<QQuickWindow *>(m_engine.rootObjects().first());
636 }
637
638 44 bool QuickMailHost::flush() {
639
3/6
✓ Branch 3 → 4 taken 44 times.
✗ Branch 3 → 7 not taken.
✓ Branch 6 → 7 taken 44 times.
✗ Branch 6 → 8 not taken.
✓ Branch 9 → 10 taken 44 times.
✗ Branch 9 → 11 not taken.
44 if (!m_persistence || m_persistence->flush()) return true;
640 ✗ m_error = m_persistence->error();
641 ✗ return false;
642 }
643