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: 90.9% 70 / 0 / 77
Functions: 100.0% 2 / 0 / 2
Branches: 66.0% 157 / 0 / 238

presentation/TaskEditorFields.cpp
Line Branch Exec Source
1 #include "TaskEditorFields.h"
2 #include "PimEditorTime.h"
3 #include "util/MessageId.h"
4 #include "data/RecurrenceRule.h"
5 #include <optional>
6 #include <cmath>
7 32 bool TaskEditorFields::apply(CalendarTask &task, const QVariantMap &fields) {
8 32 const QString previousStatus = task.status; const int previousPercent = task.percentComplete;
9
1/1
✓ Branch 3 → 4 taken 32 times.
32 const bool hadSummary = !task.summary.trimmed().isEmpty();
10
2/2
✓ Branch 6 → 7 taken 32 times.
✓ Branch 7 → 8 taken 32 times.
32 const bool hadDue = task.due.isValid(), hadStart = task.dtStart.isValid();
11
3/3
✓ Branch 12 → 13 taken 32 times.
✓ Branch 13 → 14 taken 32 times.
✓ Branch 14 → 15 taken 32 times.
64 task.summary = fields.value(QStringLiteral("summary"), task.summary).toString().trimmed();
12
2/2
✓ Branch 26 → 27 taken 32 times.
✓ Branch 27 → 28 taken 32 times.
64 task.description = fields.value(QStringLiteral("description"), task.description).toString();
13 const QString related = MessageId::canonicalFirst(
14
1/1
✓ Branch 38 → 39 taken 32 times.
96 fields.value(QStringLiteral("relatedMessageId"), task.relatedMessageId)
15
2/2
✓ Branch 39 → 40 taken 32 times.
✓ Branch 40 → 41 taken 32 times.
32 .toString());
16
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 32 times.
32 if (related.size() > 998)
17 ✗ return false;
18 32 task.relatedMessageId = related;
19 // S106-348: SUMMARY is optional in RFC 5545 — a synced task without one
20 // stays editable; only clearing a title that was there is refused (a new
21 // task's title is required by its caller).
22
6/6
✓ Branch 51 → 52 taken 3 times.
✓ Branch 51 → 54 taken 29 times.
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 54 taken 2 times.
✓ Branch 55 → 56 taken 1 time.
✓ Branch 55 → 57 taken 31 times.
32 if (task.summary.isEmpty() && hadSummary) return false;
23 62 const auto wholeNumber = [&fields](const QString &key, int fallback, int maximum) -> std::optional<int> {
24 62 bool ok = false;
25
2/2
✓ Branch 3 → 4 taken 62 times.
✓ Branch 4 → 5 taken 62 times.
62 const double value = fields.value(key, fallback).toDouble(&ok);
26
11/12
✓ Branch 7 → 8 taken 61 times.
✓ Branch 7 → 13 taken 1 time.
✓ Branch 9 → 10 taken 61 times.
✗ Branch 9 → 13 not taken.
✓ Branch 10 → 11 taken 60 times.
✓ Branch 10 → 13 taken 1 time.
✓ Branch 11 → 12 taken 59 times.
✓ Branch 11 → 13 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 58 times.
✓ Branch 15 → 16 taken 4 times.
✓ Branch 15 → 19 taken 58 times.
62 if (!ok || !std::isfinite(value) || value < 0 || value > maximum || std::floor(value) != value)
27 4 return std::nullopt;
28 58 return static_cast<int>(value);
29 31 };
30
1/1
✓ Branch 60 → 61 taken 31 times.
62 const auto priority = wholeNumber(QStringLiteral("priority"), task.priority, 9);
31
1/1
✓ Branch 66 → 67 taken 31 times.
62 const auto percent = wholeNumber(QStringLiteral("percentComplete"), task.percentComplete, 100);
32
5/6
✓ Branch 70 → 71 taken 31 times.
✗ Branch 70 → 73 not taken.
✓ Branch 72 → 73 taken 4 times.
✓ Branch 72 → 74 taken 27 times.
✓ Branch 75 → 76 taken 4 times.
✓ Branch 75 → 77 taken 27 times.
31 if (!priority || !percent) return false;
33 27 task.priority = *priority;
34
5/6
✓ Branch 79 → 80 taken 5 times.
✓ Branch 79 → 84 taken 22 times.
✓ Branch 82 → 83 taken 5 times.
✗ Branch 82 → 84 not taken.
✓ Branch 85 → 86 taken 5 times.
✓ Branch 85 → 115 taken 22 times.
27 if (*percent != task.percentComplete && task.status != QLatin1String("CANCELLED")) {
35 5 task.percentComplete = *percent;
36
2/2
✓ Branch 88 → 89 taken 3 times.
✓ Branch 88 → 92 taken 2 times.
18 task.status = *percent == 100 ? QStringLiteral("COMPLETED")
37
8/8
✓ Branch 93 → 94 taken 1 time.
✓ Branch 93 → 97 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
✓ Branch 102 → 104 taken 4 times.
✓ Branch 104 → 105 taken 1 time.
✓ Branch 104 → 106 taken 4 times.
✓ Branch 106 → 107 taken 3 times.
✓ Branch 106 → 108 taken 2 times.
12 : *percent == 0 ? QStringLiteral("NEEDS-ACTION") : QStringLiteral("IN-PROCESS");
38
3/3
✓ Branch 109 → 110 taken 3 times.
✓ Branch 109 → 111 taken 2 times.
✓ Branch 110 → 112 taken 3 times.
5 task.completedAt = *percent == 100 ? QDateTime::currentDateTimeUtc() : QDateTime{};
39 }
40
3/3
✓ Branch 118 → 119 taken 27 times.
✓ Branch 121 → 122 taken 4 times.
✓ Branch 121 → 162 taken 23 times.
27 if (fields.contains(QStringLiteral("status"))) {
41
2/2
✓ Branch 126 → 127 taken 4 times.
✓ Branch 127 → 128 taken 4 times.
8 const auto status = fields.value(QStringLiteral("status")).toString();
42
3/6
✓ Branch 140 → 141 taken 16 times.
✓ Branch 140 → 142 taken 4 times.
✗ Branch 142 → 143 not taken.
✓ Branch 142 → 144 taken 4 times.
✗ Branch 497 → 498 not taken.
✗ Branch 497 → 499 not taken.
20 if (!QStringList{"NEEDS-ACTION", "IN-PROCESS", "COMPLETED", "CANCELLED"}.contains(status)) return false;
43 4 task.status = status;
44
3/3
✓ Branch 147 → 148 taken 2 times.
✓ Branch 147 → 152 taken 2 times.
✓ Branch 148 → 149 taken 2 times.
4 if (status == QLatin1String("COMPLETED")) { task.percentComplete = 100; task.completedAt = QDateTime::currentDateTimeUtc(); }
45 2 else task.completedAt = {};
46 4 }
47
5/6
✓ Branch 163 → 164 taken 3 times.
✓ Branch 163 → 167 taken 24 times.
✓ Branch 165 → 166 taken 3 times.
✗ Branch 165 → 167 not taken.
✓ Branch 168 → 169 taken 3 times.
✓ Branch 168 → 171 taken 24 times.
27 if (task.status == "COMPLETED" && previousStatus != "COMPLETED") { task.previousStatus = previousStatus; task.previousPercent = previousPercent; }
48
4/4
✓ Branch 171 → 172 taken 27 times.
✓ Branch 176 → 177 taken 27 times.
✓ Branch 177 → 178 taken 27 times.
✓ Branch 178 → 179 taken 27 times.
54 const QString dueText = fields.value(QStringLiteral("due"), PimEditorTime::display(task.due, task.dueAllDay)).toString().trimmed();
49
2/2
✓ Branch 186 → 187 taken 17 times.
✓ Branch 186 → 191 taken 10 times.
27 if (dueText.isEmpty()) {
50 17 task.due = {};
51 17 task.dueAllDay = false;
52 } else {
53 10 const bool dateOnly = dueText.size() == 10;
54
1/1
✓ Branch 192 → 193 taken 10 times.
10 const QDateTime due = PimEditorTime::parse(dueText, dateOnly);
55
2/3
✓ Branch 193 → 194 taken 10 times.
✗ Branch 194 → 195 not taken.
✓ Branch 194 → 196 taken 10 times.
10 if (!due.isValid()) return false;
56 // A title-only save keeps the original zone, including custom TZIDs.
57
3/3
✓ Branch 196 → 197 taken 10 times.
✓ Branch 199 → 200 taken 7 times.
✓ Branch 199 → 213 taken 3 times.
10 if (dueText != PimEditorTime::display(task.due, task.dueAllDay))
58
7/13
✓ Branch 200 → 201 taken 2 times.
✓ Branch 200 → 207 taken 5 times.
✓ Branch 201 → 202 taken 2 times.
✓ Branch 202 → 203 taken 1 time.
✓ Branch 202 → 207 taken 1 time.
✗ Branch 203 → 204 not taken.
✓ Branch 203 → 207 taken 1 time.
✗ Branch 204 → 205 not taken.
✗ Branch 205 → 206 not taken.
✗ Branch 210 → 211 not taken.
✓ Branch 210 → 212 taken 7 times.
✗ Branch 530 → 531 not taken.
✗ Branch 530 → 532 not taken.
7 task.due = !dateOnly && task.due.isValid() && !task.dueAllDay ? due.toTimeZone(task.due.timeZone()) : due;
59 10 task.dueAllDay = dateOnly;
60 10 }
61
4/4
✓ Branch 219 → 220 taken 27 times.
✓ Branch 220 → 221 taken 27 times.
✓ Branch 222 → 223 taken 13 times.
✓ Branch 222 → 272 taken 14 times.
27 if (fields.contains("start")) {
62
4/4
✓ Branch 224 → 225 taken 13 times.
✓ Branch 225 → 226 taken 13 times.
✓ Branch 226 → 227 taken 13 times.
✓ Branch 227 → 228 taken 13 times.
13 const QString start = fields.value("start").toString().trimmed();
63
1/2
✓ Branch 233 → 234 taken 13 times.
✗ Branch 233 → 238 not taken.
13 if (start.isEmpty()) { task.dtStart = {}; task.startAllDay = false; }
64 else {
65 ✗ const bool dateOnly = start.size() == 10; const auto parsed = PimEditorTime::parse(start, dateOnly);
66 ✗ if (!parsed.isValid()) return false;
67 ✗ if (start != PimEditorTime::display(task.dtStart, task.startAllDay))
68 ✗ task.dtStart = !dateOnly && task.dtStart.isValid() && !task.startAllDay ? parsed.toTimeZone(task.dtStart.timeZone()) : parsed;
69 ✗ task.startAllDay = dateOnly;
70 ✗ }
71 13 }
72
4/4
✓ Branch 272 → 273 taken 27 times.
✓ Branch 273 → 274 taken 27 times.
✓ Branch 275 → 276 taken 13 times.
✓ Branch 275 → 307 taken 14 times.
27 if (fields.contains("rrule")) {
73
3/3
✓ Branch 277 → 278 taken 13 times.
✓ Branch 278 → 279 taken 13 times.
✓ Branch 279 → 280 taken 13 times.
13 const QString rule = fields.value("rrule").toString();
74
3/20
✗ Branch 284 → 285 not taken.
✓ Branch 284 → 295 taken 13 times.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 295 not taken.
✗ Branch 287 → 288 not taken.
✗ Branch 288 → 289 not taken.
✗ Branch 289 → 290 not taken.
✗ Branch 289 → 294 not taken.
✗ Branch 290 → 291 not taken.
✗ Branch 291 → 292 not taken.
✗ Branch 291 → 295 not taken.
✗ Branch 292 → 293 not taken.
✗ Branch 293 → 294 not taken.
✗ Branch 293 → 295 not taken.
✗ Branch 296 → 297 not taken.
✓ Branch 296 → 298 taken 13 times.
✗ Branch 298 → 299 not taken.
✓ Branch 298 → 300 taken 13 times.
✗ Branch 577 → 578 not taken.
✗ Branch 577 → 579 not taken.
13 if (rule != task.rrule && !rule.isEmpty() && (!RecurrenceRule::parse(rule).fullySupported() || (!task.dtStart.isValid() && !task.due.isValid()))) return false;
75 13 task.rrule = rule;
76 13 }
77
4/4
✓ Branch 307 → 308 taken 27 times.
✓ Branch 308 → 309 taken 27 times.
✓ Branch 310 → 311 taken 13 times.
✓ Branch 310 → 322 taken 14 times.
27 if (fields.contains("tags")) {
78
5/5
✓ Branch 312 → 313 taken 13 times.
✓ Branch 313 → 314 taken 13 times.
✓ Branch 314 → 315 taken 13 times.
✓ Branch 320 → 321 taken 13 times.
✓ Branch 321 → 322 taken 13 times.
13 task.tags = fields.value("tags").toStringList(); task.tags.removeAll(""); task.tags.removeDuplicates();
79 }
80
3/7
✓ Branch 322 → 323 taken 27 times.
✓ Branch 323 → 324 taken 27 times.
✗ Branch 325 → 326 not taken.
✓ Branch 325 → 336 taken 27 times.
✗ Branch 327 → 328 not taken.
✗ Branch 328 → 329 not taken.
✗ Branch 329 → 330 not taken.
27 if (fields.contains("parentUid")) task.parentUid = fields.value("parentUid").toString();
81
3/7
✓ Branch 336 → 337 taken 27 times.
✓ Branch 337 → 338 taken 27 times.
✗ Branch 339 → 340 not taken.
✓ Branch 339 → 348 taken 27 times.
✗ Branch 341 → 342 not taken.
✗ Branch 342 → 343 not taken.
✗ Branch 343 → 344 not taken.
27 if (fields.contains("sortOrder")) task.sortOrder = fields.value("sortOrder").toLongLong();
82
4/4
✓ Branch 348 → 349 taken 27 times.
✓ Branch 349 → 350 taken 27 times.
✓ Branch 351 → 352 taken 2 times.
✓ Branch 351 → 391 taken 25 times.
27 if (fields.contains("alarmTimes")) {
83
1/1
✓ Branch 352 → 353 taken 2 times.
2 task.alarmTimes.clear();
84
7/7
✓ Branch 354 → 355 taken 2 times.
✓ Branch 355 → 356 taken 2 times.
✓ Branch 356 → 357 taken 2 times.
✓ Branch 360 → 361 taken 2 times.
✓ Branch 361 → 362 taken 2 times.
✓ Branch 384 → 363 taken 1 time.
✓ Branch 384 → 385 taken 2 times.
3 for (const auto &value : fields.value("alarmTimes").toList()) {
85
5/11
✓ Branch 364 → 365 taken 1 time.
✓ Branch 365 → 366 taken 1 time.
✓ Branch 366 → 367 taken 1 time.
✗ Branch 366 → 368 not taken.
✓ Branch 367 → 370 taken 1 time.
✗ Branch 368 → 369 not taken.
✗ Branch 369 → 370 not taken.
✗ Branch 370 → 371 not taken.
✓ Branch 370 → 372 taken 1 time.
✗ Branch 634 → 635 not taken.
✗ Branch 634 → 636 not taken.
1 const auto stamp = value.toDateTime().isValid() ? value.toDateTime() : QDateTime::fromString(value.toString(), Qt::ISODate);
86
2/3
✓ Branch 373 → 374 taken 1 time.
✗ Branch 374 → 375 not taken.
✓ Branch 374 → 376 taken 1 time.
1 if (!stamp.isValid()) return false;
87
1/1
✓ Branch 376 → 377 taken 1 time.
1 task.alarmTimes << stamp;
88 3 }
89 }
90
3/11
✓ Branch 391 → 392 taken 27 times.
✗ Branch 392 → 393 not taken.
✓ Branch 392 → 399 taken 27 times.
✗ Branch 393 → 394 not taken.
✗ Branch 394 → 395 not taken.
✗ Branch 394 → 399 not taken.
✗ Branch 395 → 396 not taken.
✗ Branch 397 → 398 not taken.
✗ Branch 397 → 399 not taken.
✗ Branch 400 → 401 not taken.
✓ Branch 400 → 402 taken 27 times.
27 if (task.dtStart.isValid() && task.due.isValid() && task.dtStart > task.due) return false;
91 // S106-349: removing the last date of a repeating task leaves its rule
92 // without an anchor (completion then refuses it) — refused like the editor.
93
9/14
✓ Branch 403 → 404 taken 1 time.
✓ Branch 403 → 411 taken 26 times.
✓ Branch 404 → 405 taken 1 time.
✓ Branch 405 → 406 taken 1 time.
✗ Branch 405 → 411 not taken.
✓ Branch 406 → 407 taken 1 time.
✓ Branch 407 → 408 taken 1 time.
✗ Branch 407 → 411 not taken.
✗ Branch 408 → 409 not taken.
✓ Branch 408 → 410 taken 1 time.
✗ Branch 409 → 410 not taken.
✗ Branch 409 → 411 not taken.
✓ Branch 412 → 413 taken 1 time.
✓ Branch 412 → 414 taken 26 times.
27 if (!task.rrule.isEmpty() && !task.due.isValid() && !task.dtStart.isValid() && (hadDue || hadStart)) return false;
94 26 return true;
95 36 }
96