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: 95.9% 141 / 0 / 147
Functions: 100.0% 15 / 0 / 15
Branches: 80.4% 225 / 15 / 295

service/ImapServiceSearch.cpp
Line Branch Exec Source
1 // UID SEARCH commands of ImapService (T-187 text search, Sprint 59 facets,
2 // Sprint 101 negations, T-211 Message-ID lookup) and their tagged result,
3 // split from ImapService.cpp for its line limit (SPRINT-92.md §8).
4
5 #include "ImapService.h"
6 #include "ImapCommandTable.h"
7 #include "ImapResponseParser.h"
8
9 #include <QLocale>
10 #include <utility>
11 #include <QLoggingCategory>
12
13 Q_DECLARE_LOGGING_CATEGORY(lcImap)
14
15 7 void ImapService::searchAllUids(qint64 fromUid) {
16
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 14 taken 5 times.
7 if (m_state != State::Selected) {
17 4 qCWarning(lcImap) << "Cannot search: no folder selected"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; folder guard remains measured.
18 2 return;
19 }
20
2/2
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 22 taken 3 times.
5 if (m_commands->hasStatefulCommandInFlight()) {
21
1/1
✓ Branch 19 → 20 taken 2 times.
4 m_commands->enqueueSerializedCommand([this, fromUid]() { searchAllUids(fromUid); });
22 2 return;
23 }
24
25 3 m_pendingSearchUids.clear();
26
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 37 taken 2 times.
3 if (fromUid > 1) {
27 // Delta search: only UIDs >= fromUid
28 1 m_activeMessageIdSearch = {};
29
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
3 m_commands->sendCommand("SEARCH",
30
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
3 QString("UID SEARCH UID %1:*").arg(fromUid));
31 } else {
32 2 m_activeMessageIdSearch = {};
33
3/3
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
2 m_commands->sendCommand("SEARCH", "UID SEARCH ALL");
34 }
35 }
36
37 // T-187: IMAP text-based SEARCH
38 39 void ImapService::searchText(const QString &query, const QString &criteria) {
39 // S106-025: the search key is part of the command line and is not quoted —
40 // only the RFC 3501 text keys are accepted, never arbitrary text.
41 5 static const QStringList textKeys = {QStringLiteral("SUBJECT"), QStringLiteral("FROM"),
42 5 QStringLiteral("TO"), QStringLiteral("CC"),
43 5 QStringLiteral("BCC"), QStringLiteral("BODY"),
44
5/10
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 40 taken 34 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 40 not taken.
✓ Branch 30 → 31 taken 35 times.
✓ Branch 30 → 32 taken 5 times.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
84 QStringLiteral("TEXT")};
45
2/2
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 56 taken 38 times.
39 if (!textKeys.contains(criteria)) {
46
7/7
✓ Branch 42 → 43 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 54 → 44 taken 1 time.
✓ Branch 54 → 55 taken 1 time.
2 qCWarning(lcImap) << "searchText: refused search key" << criteria.left(20);
47 1 return;
48 }
49
2/2
✓ Branch 56 → 57 taken 3 times.
✓ Branch 56 → 68 taken 35 times.
38 if (m_state != State::Selected) {
50
5/5
✓ Branch 57 → 58 taken 3 times.
✓ Branch 61 → 62 taken 3 times.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 66 → 59 taken 3 times.
✓ Branch 66 → 67 taken 3 times.
6 qCWarning(lcImap) << "Cannot search: no folder selected";
51 3 return;
52 }
53
2/2
✓ Branch 70 → 71 taken 4 times.
✓ Branch 70 → 79 taken 31 times.
35 if (m_commands->hasStatefulCommandInFlight()) {
54
2/4
✓ Branch 74 → 75 taken 4 times.
✓ Branch 75 → 76 taken 4 times.
✗ Branch 146 → 147 not taken.
✗ Branch 146 → 148 not taken.
4 m_commands->enqueueSerializedCommand([this, query, criteria]() {
55 2 searchText(query, criteria);
56 2 });
57 4 return;
58 }
59
60 31 m_pendingSearchUids.clear();
61 // RFC 3501: SEARCH <criteria> <quoted-string>
62 31 m_activeMessageIdSearch = {};
63
2/2
✓ Branch 86 → 87 taken 31 times.
✓ Branch 87 → 88 taken 31 times.
93 m_commands->sendCommand("SEARCH",
64
3/3
✓ Branch 83 → 84 taken 31 times.
✓ Branch 84 → 85 taken 31 times.
✓ Branch 85 → 86 taken 31 times.
62 QString("UID SEARCH %1 %2").arg(criteria, quoteImapString(query)));
65 40 }
66
67 // Sprint 59 (S2): composite SEARCH for the visual search facets.
68 50 bool ImapService::SearchCriteria::isEmpty() const {
69
7/8
✓ Branch 5 → 6 taken 26 times.
✓ Branch 5 → 34 taken 5 times.
✓ Branch 7 → 8 taken 24 times.
✓ Branch 7 → 34 taken 2 times.
✓ Branch 9 → 10 taken 24 times.
✗ Branch 9 → 34 not taken.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 34 taken 3 times.
81 return text.isEmpty() && from.isEmpty() && to.isEmpty() && listId.isEmpty() && subject.isEmpty() &&
70
6/6
✓ Branch 13 → 14 taken 18 times.
✓ Branch 13 → 34 taken 3 times.
✓ Branch 15 → 16 taken 16 times.
✓ Branch 15 → 34 taken 2 times.
✓ Branch 16 → 17 taken 13 times.
✓ Branch 16 → 34 taken 3 times.
21 !since.isValid() && !before.isValid() && unread == SearchTri::Any &&
71
6/6
✓ Branch 17 → 18 taken 11 times.
✓ Branch 17 → 34 taken 2 times.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 18 → 34 taken 2 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 34 taken 5 times.
22 flagged == SearchTri::Any && answered == SearchTri::Any &&
72
4/8
✓ Branch 22 → 23 taken 4 times.
✗ Branch 22 → 34 not taken.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 34 not taken.
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 34 not taken.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 34 not taken.
17 keywords.isEmpty() && notText.isEmpty() && notFrom.isEmpty() && notTo.isEmpty() &&
73
4/6
✓ Branch 3 → 4 taken 31 times.
✓ Branch 3 → 34 taken 19 times.
✓ Branch 30 → 31 taken 4 times.
✗ Branch 30 → 34 not taken.
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 34 not taken.
85 notListId.isEmpty() && notSubject.isEmpty() && notKeywords.isEmpty();
74 }
75
76 30 QString ImapService::buildSearchCommand(const SearchCriteria &criteria) {
77
3/3
✓ Branch 2 → 3 taken 30 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 28 times.
30 if (criteria.isEmpty())
78 2 return QString();
79
80 // RFC 3501 date format is "dd-MMM-yyyy" with English month names; force the C
81 // locale so it never gets localized (e.g. "Feb" not "Févr").
82 8 auto imapDate = [](const QDate &d) {
83
2/2
✓ Branch 2 → 3 taken 8 times.
✓ Branch 6 → 7 taken 8 times.
16 return QLocale::c().toString(d, QStringLiteral("dd-MMM-yyyy"));
84 };
85
86 28 QStringList parts;
87
2/2
✓ Branch 6 → 7 taken 11 times.
✓ Branch 6 → 17 taken 17 times.
28 if (!criteria.text.isEmpty())
88
3/3
✓ Branch 10 → 11 taken 11 times.
✓ Branch 11 → 12 taken 11 times.
✓ Branch 12 → 13 taken 11 times.
11 parts << QStringLiteral("TEXT") << quoteImapString(criteria.text);
89
2/2
✓ Branch 18 → 19 taken 5 times.
✓ Branch 18 → 29 taken 23 times.
28 if (!criteria.from.isEmpty())
90
3/3
✓ Branch 22 → 23 taken 5 times.
✓ Branch 23 → 24 taken 5 times.
✓ Branch 24 → 25 taken 5 times.
5 parts << QStringLiteral("FROM") << quoteImapString(criteria.from);
91
2/2
✓ Branch 30 → 31 taken 2 times.
✓ Branch 30 → 50 taken 26 times.
28 if (!criteria.to.isEmpty()) // "to:" means To or Cc, as in the local search
92
6/6
✓ Branch 34 → 35 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
4 parts << QStringLiteral("OR TO") << quoteImapString(criteria.to) << QStringLiteral("CC") << quoteImapString(criteria.to);
93
1/2
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 68 taken 28 times.
28 if (!criteria.listId.isEmpty())
94 ✗ parts << QStringLiteral("HEADER") << QStringLiteral("List-Id") << quoteImapString(criteria.listId);
95
2/2
✓ Branch 69 → 70 taken 4 times.
✓ Branch 69 → 80 taken 24 times.
28 if (!criteria.subject.isEmpty())
96
3/3
✓ Branch 73 → 74 taken 4 times.
✓ Branch 74 → 75 taken 4 times.
✓ Branch 75 → 76 taken 4 times.
4 parts << QStringLiteral("SUBJECT") << quoteImapString(criteria.subject);
97
3/3
✓ Branch 80 → 81 taken 28 times.
✓ Branch 81 → 82 taken 5 times.
✓ Branch 81 → 92 taken 23 times.
28 if (criteria.since.isValid())
98
3/3
✓ Branch 85 → 86 taken 5 times.
✓ Branch 86 → 87 taken 5 times.
✓ Branch 87 → 88 taken 5 times.
5 parts << QStringLiteral("SINCE") << imapDate(criteria.since);
99
3/3
✓ Branch 92 → 93 taken 28 times.
✓ Branch 93 → 94 taken 3 times.
✓ Branch 93 → 104 taken 25 times.
28 if (criteria.before.isValid())
100
3/3
✓ Branch 97 → 98 taken 3 times.
✓ Branch 98 → 99 taken 3 times.
✓ Branch 99 → 100 taken 3 times.
3 parts << QStringLiteral("BEFORE") << imapDate(criteria.before);
101 84 const auto state = [&parts](SearchTri value, const char *yes, const char *no) {
102
6/6
✓ Branch 2 → 3 taken 12 times.
✓ Branch 2 → 11 taken 72 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 5 times.
✓ Branch 7 → 8 taken 12 times.
✓ Branch 8 → 9 taken 12 times.
84 if (value != SearchTri::Any) parts << QString::fromLatin1(value == SearchTri::Yes ? yes : no);
103 84 };
104
1/1
✓ Branch 104 → 105 taken 28 times.
28 state(criteria.unread, "UNSEEN", "SEEN");
105
1/1
✓ Branch 105 → 106 taken 28 times.
28 state(criteria.flagged, "FLAGGED", "UNFLAGGED");
106
1/1
✓ Branch 106 → 107 taken 28 times.
28 state(criteria.answered, "ANSWERED", "UNANSWERED");
107 // S106-024: KEYWORD/UNKEYWORD take a flag keyword, an atom (RFC 3501
108 // §6.4.4) — a quoted string makes strict servers answer BAD. A keyword
109 // that is no valid atom cannot match any message flag and is left out.
110 10 const auto keywordAtom = [](const QString &keyword) {
111
1/1
✓ Branch 2 → 3 taken 10 times.
10 const QString trimmed = keyword.trimmed();
112
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 8 times.
10 if (trimmed.isEmpty())
113 2 return QString();
114
2/2
✓ Branch 27 → 8 taken 52 times.
✓ Branch 27 → 28 taken 7 times.
59 for (const QChar c : trimmed)
115
11/17
✓ Branch 9 → 10 taken 51 times.
✓ Branch 9 → 17 taken 1 time.
✓ Branch 11 → 12 taken 51 times.
✗ Branch 11 → 17 not taken.
✓ Branch 15 → 16 taken 51 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 51 times.
✓ Branch 19 → 20 taken 51 times.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 21 → 22 taken 51 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 26 taken 51 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
103 if (c.unicode() <= 0x20 || c.unicode() >= 0x7f || QStringLiteral("(){%*\"\\]").contains(c))
116 1 return QString();
117 7 return trimmed;
118 10 };
119
2/2
✓ Branch 124 → 109 taken 9 times.
✓ Branch 124 → 125 taken 28 times.
37 for (const QString &kw : criteria.keywords)
120
5/5
✓ Branch 110 → 111 taken 9 times.
✓ Branch 112 → 113 taken 6 times.
✓ Branch 112 → 121 taken 3 times.
✓ Branch 116 → 117 taken 6 times.
✓ Branch 117 → 118 taken 6 times.
15 if (const QString atom = keywordAtom(kw); !atom.isEmpty()) parts << QStringLiteral("KEYWORD") << atom;
121
2/2
✓ Branch 142 → 127 taken 1 time.
✓ Branch 142 → 143 taken 28 times.
29 for (const QString &kw : criteria.notKeywords)
122
4/5
✓ Branch 128 → 129 taken 1 time.
✓ Branch 130 → 131 taken 1 time.
✗ Branch 130 → 139 not taken.
✓ Branch 134 → 135 taken 1 time.
✓ Branch 135 → 136 taken 1 time.
2 if (const QString atom = keywordAtom(kw); !atom.isEmpty()) parts << QStringLiteral("UNKEYWORD") << atom;
123 // Sprint 101 D-14: negations (RFC 3501 NOT …).
124 ✗ const QList<QPair<const QStringList *, QString>> negated = {{&criteria.notText, "NOT TEXT"},
125 ✗ {&criteria.notFrom, "NOT FROM"}, {&criteria.notSubject, "NOT SUBJECT"},
126
2/4
✓ Branch 149 → 150 taken 112 times.
✓ Branch 149 → 151 taken 28 times.
✗ Branch 328 → 329 not taken.
✗ Branch 328 → 330 not taken.
140 {&criteria.notListId, "NOT HEADER List-Id"}};
127
2/2
✓ Branch 173 → 153 taken 112 times.
✓ Branch 173 → 174 taken 28 times.
140 for (const auto &[values, key] : negated)
128
1/8
✗ Branch 159 → 160 not taken.
✗ Branch 162 → 163 not taken.
✗ Branch 162 → 168 not taken.
✗ Branch 163 → 164 not taken.
✗ Branch 164 → 165 not taken.
✗ Branch 165 → 166 not taken.
✗ Branch 170 → 158 not taken.
✓ Branch 170 → 171 taken 112 times.
112 for (const QString &value : *values) if (!value.trimmed().isEmpty()) parts << key << quoteImapString(value);
129
1/2
✗ Branch 196 → 176 not taken.
✓ Branch 196 → 197 taken 28 times.
28 for (const QString &value : criteria.notTo)
130 ✗ if (!value.trimmed().isEmpty()) parts << "NOT OR TO" << quoteImapString(value) << "CC" << quoteImapString(value);
131
2/2
✓ Branch 198 → 199 taken 1 time.
✓ Branch 198 → 200 taken 27 times.
28 if (parts.isEmpty())
132 1 return QString();
133 // S106-024: RFC 3501 searches in US-ASCII unless CHARSET names another
134 // character set; terms with umlauts were sent without it, and a strict
135 // server answered NO [BADCHARSET].
136
1/1
✓ Branch 202 → 203 taken 27 times.
27 const QString joined = parts.join(QLatin1Char(' '));
137 27 bool ascii = true;
138
2/2
✓ Branch 209 → 205 taken 739 times.
✓ Branch 209 → 210 taken 24 times.
763 for (const QChar c : joined)
139
2/2
✓ Branch 206 → 207 taken 3 times.
✓ Branch 206 → 208 taken 736 times.
739 if (c.unicode() > 0x7f) { ascii = false; break; }
140
7/11
✓ Branch 210 → 211 taken 24 times.
✓ Branch 210 → 214 taken 3 times.
✓ Branch 217 → 218 taken 27 times.
✓ Branch 220 → 221 taken 3 times.
✓ Branch 220 → 222 taken 24 times.
✓ Branch 222 → 223 taken 24 times.
✓ Branch 222 → 224 taken 3 times.
✗ Branch 365 → 366 not taken.
✗ Branch 365 → 367 not taken.
✗ Branch 369 → 370 not taken.
✗ Branch 369 → 371 not taken.
54 return (ascii ? QStringLiteral("UID SEARCH ") : QStringLiteral("UID SEARCH CHARSET UTF-8 ")) + joined;
141 56 }
142
143 13 void ImapService::search(const SearchCriteria &criteria) {
144
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 14 taken 12 times.
13 if (m_state != State::Selected) {
145
5/5
✓ Branch 3 → 4 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 12 → 5 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
2 qCWarning(lcImap) << "Cannot search: no folder selected";
146 11 return;
147 }
148
1/1
✓ Branch 14 → 15 taken 12 times.
12 const QString command = buildSearchCommand(criteria);
149
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 28 taken 11 times.
12 if (command.isEmpty()) {
150 2 qCInfo(lcImap) << "search: no server-mappable criteria — skipping"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; criteria guard remains measured.
151 1 return;
152 }
153
3/3
✓ Branch 29 → 30 taken 11 times.
✓ Branch 30 → 31 taken 9 times.
✓ Branch 30 → 38 taken 2 times.
11 if (m_commands->hasStatefulCommandInFlight()) {
154
2/2
✓ Branch 33 → 34 taken 9 times.
✓ Branch 34 → 35 taken 9 times.
11 m_commands->enqueueSerializedCommand([this, criteria]() { search(criteria); });
155 9 return;
156 }
157
158
1/1
✓ Branch 38 → 39 taken 2 times.
2 m_pendingSearchUids.clear();
159 2 m_activeMessageIdSearch = {};
160
2/2
✓ Branch 42 → 43 taken 2 times.
✓ Branch 43 → 44 taken 2 times.
2 m_commands->sendCommand("SEARCH", command);
161 12 }
162
163 // T-211: Search by Message-ID header (for undo-move)
164 17 quint64 ImapService::searchByMessageId(const QString &messageId) {
165 17 return sendMessageIdSearch(messageId, ++m_nextMessageIdSearch);
166 }
167
168 19 quint64 ImapService::sendMessageIdSearch(const QString &messageId, quint64 sequence) {
169
2/2
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 14 taken 10 times.
19 if (m_state != State::Selected) {
170 18 qCWarning(lcImap) << "Cannot search: no folder selected"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; folder guard remains measured.
171 9 return 0;
172 }
173
2/2
✓ Branch 16 → 17 taken 4 times.
✓ Branch 16 → 24 taken 6 times.
10 if (m_commands->hasStatefulCommandInFlight()) {
174
1/1
✓ Branch 20 → 21 taken 4 times.
8 m_commands->enqueueSerializedCommand(
175
1/3
✓ Branch 19 → 20 taken 4 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
10 [this, messageId, sequence]() { sendMessageIdSearch(messageId, sequence); });
176 4 return sequence;
177 }
178
179 6 m_pendingSearchUids.clear();
180 6 m_activeMessageIdSearch = {sequence, m_selectedFolder, m_selectedUidValidity};
181 // RFC 3501: UID SEARCH HEADER Message-ID <message-id>
182
2/2
✓ Branch 35 → 36 taken 6 times.
✓ Branch 36 → 37 taken 6 times.
12 m_commands->sendCommand("SEARCH",
183 12 QStringLiteral("UID SEARCH HEADER Message-ID %1")
184
2/2
✓ Branch 33 → 34 taken 6 times.
✓ Branch 34 → 35 taken 6 times.
18 .arg(quoteImapString(messageId)));
185 6 return sequence;
186 6 }
187
188 48 void ImapService::handleTaggedSearch(const TaggedResponse &response) {
189 48 const MessageIdSearch identified = std::exchange(m_activeMessageIdSearch, {});
190
2/2
✓ Branch 5 → 6 taken 46 times.
✓ Branch 5 → 23 taken 2 times.
48 if (response.status == "OK") {
191
6/6
✓ Branch 6 → 7 taken 46 times.
✓ Branch 10 → 11 taken 46 times.
✓ Branch 11 → 12 taken 46 times.
✓ Branch 13 → 14 taken 46 times.
✓ Branch 18 → 8 taken 46 times.
✓ Branch 18 → 19 taken 46 times.
92 qCInfo(lcImap) << "SEARCH complete:" << m_pendingSearchUids.size()
192
1/1
✓ Branch 14 → 15 taken 46 times.
46 << "UIDs";
193
2/2
✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 21 taken 40 times.
46 if (identified.sequence)
194 6 emit messageIdSearchFinished(identified.sequence, identified.folder,
195
1/1
✓ Branch 20 → 21 taken 6 times.
6 identified.uidValidity, m_pendingSearchUids, true);
196
1/1
✓ Branch 21 → 22 taken 46 times.
46 emit searchResultReceived(m_pendingSearchUids);
197
1/1
✓ Branch 22 → 42 taken 46 times.
46 m_pendingSearchUids.clear();
198 } else {
199
6/6
✓ Branch 23 → 24 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 33 → 25 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
4 qCWarning(lcImap) << "SEARCH failed:" << response.message;
200 // S106-024: say that the server refused — the empty result below alone
201 // looked exactly like "no matches".
202
1/1
✓ Branch 34 → 35 taken 2 times.
2 emit searchFailed(response.message);
203 // Still emit a (empty) result so multi-folder search flows advance
204 // instead of waiting forever for a result that will never arrive.
205
1/1
✓ Branch 35 → 36 taken 2 times.
2 m_pendingSearchUids.clear();
206
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 41 taken 2 times.
2 if (identified.sequence)
207 ✗ emit messageIdSearchFinished(identified.sequence, identified.folder,
208 ✗ identified.uidValidity, {}, false);
209
1/1
✓ Branch 41 → 42 taken 2 times.
2 emit searchResultReceived(m_pendingSearchUids);
210 }
211 // Drain deferred commands (e.g. body fetch queued during SEARCH)
212
2/2
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 48 taken 46 times.
48 if (!m_commands->deferredCommands().isEmpty()) {
213
1/1
✓ Branch 47 → 48 taken 2 times.
2 m_commands->runNextDeferredCommand();
214 }
215 48 }
216