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: 76.9% 10 / 0 / 13
Functions: 100.0% 3 / 0 / 3
Branches: 61.1% 11 / 0 / 18

data/ContactQueries.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QList>
5 #include <QSqlDatabase>
6 #include <QString>
7 #include <QStringList>
8 #include <algorithm>
9 #include <optional>
10
11 // Read-only contact queries (Sprint 97 S97.1.2/S97.2.2): paged lists per
12 // view, letter sections, search, completion and the address index. Works on
13 // a contacts.db connection owned by the caller, so the list worker and the
14 // GUI thread each use their own connection (worker-safe).
15 //
16 // A list row is a card (cardId > 0) or, in "Collected addresses" and
17 // "Frequently contacted", a collected address (cardId == 0). Its key is
18 // "<bookId>/<uid>" or "collected:<address>" (S97.1.7, identity not rows).
19 namespace ContactQueries {
20
21 // S106-310: sort keys in SQLite BINARY order (UTF-8 bytes = code point
22 // order). QString's < compares UTF-16 units, which puts a supplementary
23 // character (surrogates 0xD800…) before U+E000…U+FFFF; at the first
24 // differing unit surrogates move above 0xE000…0xFFFF (ICU's code point
25 // order fix-up). Every in-memory order next to a keyset page uses this.
26 642 inline bool sortKeyLess(const QString &a, const QString &b) {
27 1038 const auto rank = [](char16_t unit) {
28
6/6
✓ Branch 2 → 3 taken 52 times.
✓ Branch 2 → 5 taken 986 times.
✓ Branch 3 → 4 taken 22 times.
✓ Branch 3 → 5 taken 30 times.
✓ Branch 5 → 6 taken 30 times.
✓ Branch 5 → 7 taken 986 times.
1038 return unit >= 0xD800 && unit <= 0xDFFF ? int(unit) + 0x2000 : unit >= 0xE000 ? int(unit) - 0x800 : int(unit);
29 };
30 642 const qsizetype common = std::min(a.size(), b.size());
31
2/2
✓ Branch 19 → 6 taken 1344 times.
✓ Branch 19 → 20 taken 123 times.
1467 for (qsizetype i = 0; i < common; ++i)
32
2/2
✓ Branch 9 → 10 taken 519 times.
✓ Branch 9 → 18 taken 825 times.
1344 if (a.at(i) != b.at(i))
33 519 return rank(a.at(i).unicode()) < rank(b.at(i).unicode());
34 123 return a.size() < b.size();
35 }
36
37 // Duplicates (S97.5.9): cards that share the folded name or an address with
38 // another card of the same book, in the books shown in "All contacts".
39 // Ungrouped (F100-11): cards of a book that are in no group.
40 enum class ViewKind { All, Frequent, Collected, Book, Group, Duplicates, Ungrouped };
41
42 struct View {
43 9 ViewKind kind = ViewKind::All;
44 ✗ qint64 bookId = 0; // Book, Group
45 ✗ QString group; // Group: CATEGORIES name or the group card's name
46 ✗ bool byFamilyName = false; // "View ▸ Sort by last name" (D-13)
47
1/8
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 4 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
9 bool operator==(const View &) const = default;
48 };
49
50 struct Row {
51 QString key;
52 qint64 cardId = 0;
53 qint64 bookId = 0;
54 QString uid;
55 QString displayName;
56 QString sortKey; // folded, keyset paging
57 QString section; // "A"…"Z", "#"
58 QString organization;
59 QString primaryAddress;
60 int addressCount = 0;
61 QString bookName; // server display name, "" for the local book
62 QString accountId;
63 bool bookReadOnly = false;
64 bool localBook = false;
65 QString kind; // individual, group, org
66 QString syncState; // "", pending, conflict, legacy
67 bool writable = true;
68 bool hasPhoto = false;
69 QString photoTag; // changes when the photo does (avatar URL)
70 bool collected = false; // a collected address, not a card
71 int useCount = 0;
72 // F100-10: optional list columns.
73 QString phone;
74 QString title;
75 QString locality;
76 QString birthday; // ISO "YYYY-MM-DD" or "--MM-DD"
77 QString groups; // group names, ", "-separated
78 bool operator==(const Row &) const = default;
79 };
80
81 struct Group {
82 qint64 bookId = 0;
83 QString name;
84 qint64 cardId = 0; // group card (KIND:group / Apple), 0 for a CATEGORIES group
85 int memberCount = 0;
86 };
87
88 struct Completion {
89 QString key; // card key or "collected:<address>"
90 QString name;
91 QString address;
92 QString bookName; // "" for collected addresses
93 bool collected = false;
94 bool group = false; // S97.5.4: a group that expands into members
95 QStringList members; // group: "Name <address>" of each member
96 int useCount = 0;
97 QString uid;
98 int avatarColor = 0;
99 };
100
101 struct Lookup {
102 bool known = false; // the address belongs to a card
103 bool collected = false;
104 QString key;
105 QString displayName;
106 QString bookName;
107 qint64 bookId = 0;
108 QString uid;
109 };
110
111 // Words of `filter` all match the folded search text.
112 int count(QSqlDatabase db, const View &view, const QString &filter);
113 // F-61: afterRowKey is the collected rows' tie-break (their cardId is 0).
114 QList<Row> page(QSqlDatabase db, const View &view, const QString &filter, int limit, const QString &afterKey = {},
115 qint64 afterId = 0, const QString &afterRowKey = {});
116 // Rows of the given card ids in this view (incremental updates); a card
117 // that no longer belongs to the view is missing from the result.
118 QList<Row> rowsForCards(QSqlDatabase db, const View &view, const QString &filter, const QList<qint64> &cardIds);
119 // Sidebar counts: book id → card count, plus the smart lists.
120 QHash<qint64, int> bookCounts(QSqlDatabase db);
121 // F100-11: per book, the cards in no group (books with groups only).
122 QHash<qint64, int> ungroupedCounts(QSqlDatabase db);
123 int allCount(QSqlDatabase db);
124 int frequentCount(QSqlDatabase db);
125 int collectedCount(QSqlDatabase db);
126 QList<Group> groups(QSqlDatabase db, qint64 bookId = 0);
127 // Recipient completion (usage first, then name); one suggestion per address.
128 // Groups whose name starts with the fragment come first (S97.5.4) and carry
129 // their members.
130 QList<Completion> completions(QSqlDatabase db, const QString &fragment, int limit = 8);
131 // "Name <address>" (preferred address) of every member of a group
132 // (CATEGORIES name or group card) in one book; members without an address
133 // are skipped.
134 QStringList groupMembers(QSqlDatabase db, qint64 bookId, const QString &group);
135 // Card keys of a group's members (CATEGORIES name or group card), and the
136 // group card's own key ("" for a CATEGORIES-only group).
137 QStringList groupMemberKeys(QSqlDatabase db, qint64 bookId, const QString &group);
138 QString groupCardKey(QSqlDatabase db, qint64 bookId, const QString &group);
139 // S97.5.9: other cards that are probably the same person — same address or
140 // the same folded display name — in any book (sameBook tells them apart).
141 struct Duplicate {
142 QString key;
143 QString displayName;
144 QString bookName;
145 bool sameBook = false;
146 };
147 QList<Duplicate> duplicatesOf(QSqlDatabase db, const QString &cardKey);
148 Lookup lookup(QSqlDatabase db, const QString &address);
149 // Same-address duplicates inside one book (S97.1.8): card keys that already
150 // use the address there, excluding `exceptKey`.
151 QList<Lookup> sameBookOwners(QSqlDatabase db, qint64 bookId, const QString &address, const QString &exceptKey = {});
152 // Sprint 108 S108.5.3: every address of a card, the preferred one first,
153 // then in the card's order (the address book sheet asks which one).
154 QStringList cardAddresses(QSqlDatabase db, qint64 cardId);
155 QString cardKey(qint64 bookId, const QString &uid);
156 bool splitKey(const QString &key, qint64 *bookId, QString *uid);
157
158 } // namespace ContactQueries
159