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: 15.9% 10 / 0 / 63
Functions: 25.0% 2 / 0 / 8
Branches: 6.5% 7 / 0 / 107

presentation/QuickMailBackendFolders.cpp
Line Branch Exec Source
1 // Folder operation results (create, delete, rename) of QuickMailBackend,
2 // split from QuickMailBackend.cpp for its line limit.
3 #include "QuickMailBackend.h"
4
5 #include "app/core/FolderPredictionSession.h"
6 #include "app/core/MailSessionCore.h"
7 #include "data/FolderPredictor.h"
8 #include "data/FolderPreferences.h"
9 #include "data/MailCache.h"
10 #include "service/ImapResponseParser.h"
11 #include "service/ImapService.h"
12
13 #include <QCoreApplication>
14 #include <QLoggingCategory>
15 #include <QTimer>
16 #include <algorithm>
17
18 Q_DECLARE_LOGGING_CATEGORY(lcQuickBackend)
19
20 namespace {
21 // S106-211: the learned suggestions follow a renamed folder. The rename
22 // waits for a training batch holding the predictor's writer lock; when it
23 // still finds the database busy it is tried again a little later, so the
24 // rows never stay behind under the old path.
25 ✗ void renamePredictorData(const QString &predictorPath, const QString &oldPath, const QString &newPath,
26 const QString &delimiter, int attempts) {
27 ✗ FolderPredictor predictor;
28 ✗ if (predictor.open(predictorPath) && predictor.renameFolderData(oldPath, newPath, delimiter)) return;
29 ✗ if (--attempts <= 0) {
30 ✗ qCWarning(lcQuickBackend) << "Folder suggestions could not follow a folder rename";
31 ✗ return;
32 }
33 ✗ QTimer::singleShot(1000, QCoreApplication::instance(), [=] {
34 ✗ renamePredictorData(predictorPath, oldPath, newPath, delimiter, attempts);
35 ✗ });
36 ✗ }
37 } // namespace
38
39 795 void QuickMailBackend::wireFolderOperations() {
40 795 MailSessionCore &session = m_session;
41 // Folder operation results (D1): apply the server answer locally and
42 // surface it as status feedback.
43
1/1
✓ Branch 3 → 4 taken 795 times.
795 connect(session.imap(), &ImapService::folderCreated, this, [this](const QString &path) {
44 ✗ FolderInfo info;
45 ✗ info.name = ImapResponseParser::decodeMailboxName(path);
46 ✗ info.path = path;
47 ✗ info.delimiter = m_folderActions.folderDelimiter();
48 ✗ for (const auto &existing : m_folderInfos)
49 ✗ if (existing.path == path) return; // LIST refresh will carry the real entry.
50 ✗ m_folderInfos.append(info);
51 ✗ m_availableFolders.insert(path);
52 ✗ applyFolderList(m_folderInfos, false);
53 ✗ setStatus(tr("Folder created: %1").arg(info.name));
54 ✗ });
55
1/1
✓ Branch 6 → 7 taken 795 times.
795 connect(session.imap(), &ImapService::folderDeleted, this, [this](const QString &path) {
56 ✗ m_folderInfos.erase(std::remove_if(m_folderInfos.begin(), m_folderInfos.end(),
57 ✗ [&](const FolderInfo &folder) {
58 ✗ return folder.path == path ||
59 ✗ folder.path.startsWith(path + m_folderActions.folderDelimiter());
60 }),
61 ✗ m_folderInfos.end());
62 ✗ applyFolderList(m_folderInfos, false);
63 ✗ setStatus(tr("Folder deleted: %1").arg(path));
64 ✗ });
65
1/1
✓ Branch 9 → 10 taken 795 times.
795 connect(session.imap(), &ImapService::folderRenamed, this, [this](const QString &oldPath, const QString &newPath) {
66 ✗ m_rules.folderRenamed(oldPath, newPath); // Sprint 95 S95.4.7: rules follow the folder
67 ✗ const QString delimiter = m_folderActions.folderDelimiter();
68 // F-51 (SPRINT-104): cache, move-learning and path-keyed preferences
69 // move with the folder and its children, so the renamed folder keeps
70 // its cached mail, unread subscription, learned suggestions and
71 // checkpoint instead of being re-downloaded from scratch.
72 ✗ if (!m_session.cache()->databasePath().isEmpty())
73 ✗ m_session.cache()->renameFolderPath(accountName(), oldPath, newPath, delimiter);
74 ✗ if (const QString predictorPath = m_session.predictions()->databasePath();
75 ✗ !predictorPath.isEmpty())
76 ✗ renamePredictorData(predictorPath, oldPath, newPath, delimiter, 3);
77 // Sprint 105 S105.3.4: the evidence model is rebuilt with the new paths.
78 ✗ FolderPredictionService::shared()->forgetAccount(accountName());
79 ✗ m_predictions.clear();
80 ✗ FolderPreferences::renameFolder(m_session.profileDirectory(), accountName(),
81 oldPath, newPath, delimiter);
82 ✗ for (auto &folder : m_folderInfos) {
83 ✗ if (folder.path == oldPath) {
84 ✗ folder.path = newPath;
85 ✗ folder.name = ImapResponseParser::decodeMailboxName(newPath);
86 ✗ } else if (folder.path.startsWith(oldPath + delimiter)) {
87 ✗ folder.path = newPath + folder.path.mid(oldPath.size());
88 }
89 }
90 ✗ m_availableFolders.remove(oldPath);
91 ✗ m_availableFolders.insert(newPath);
92 ✗ applyFolderList(m_folderInfos, false);
93 ✗ if (m_folderPath == oldPath) {
94 ✗ m_folderPath = newPath;
95 ✗ emit folderChanged();
96 }
97 ✗ setStatus(tr("Folder renamed: %1").arg(newPath));
98 ✗ });
99
1/1
✓ Branch 12 → 13 taken 795 times.
795 connect(session.imap(), &ImapService::folderOperationError, this,
100 795 [this](const QString &operation, const QString &error) {
101
3/3
✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 4 → 5 taken 8 times.
8 setStatus(tr("Folder %1 failed: %2").arg(operation, error));
102 8 });
103 795 }
104