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.1% 77 / 0 / 81
Functions: 92.9% 13 / 0 / 14
Branches: 56.2% 86 / 0 / 153

data/ContactModels.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QByteArray>
4 #include <QDateTime>
5 #include <QList>
6 #include <QMetaType>
7 #include <QString>
8 #include <QStringList>
9
10 #include "DavPushModels.h"
11 #include "data/ContactPeopleModels.h"
12
13 // Sprint 97 contact value types (S97.1.1, S97.1.2, decision D-1):
14 // one ContactCard is one vCard — a person, an organisation or a group.
15 // Addresses, numbers and groups are values of that card. The raw vCard is
16 // the source of truth; ContactCard is its interpreted view (VCardCodec).
17
18 // EMAIL, TEL, URL and IMPP values.
19 struct ContactValue {
20 477 QString value; // unescaped; TEL without a "tel:" URI prefix
21 477 QStringList types; // lower case ("work", "home", "cell"), without "pref"/"internet"
22 477 bool preferred = false;
23 477 QString label; // Apple/Google X-ABLabel of the property's group
24 // Position of the property in the parsed card; editors keep it so the
25 // codec patches exactly that line (-1 = a value the user added).
26 477 int sourceIndex = -1;
27
5/10
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 477 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 477 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 477 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 477 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 477 times.
477 bool operator==(const ContactValue &) const = default;
28 };
29
30 // ADR (structured postal address).
31 struct ContactAddress {
32 117 QString poBox;
33 117 QString extended;
34 117 QString street;
35 117 QString locality;
36 117 QString region;
37 117 QString postalCode;
38 117 QString country;
39 117 QStringList types;
40 117 bool preferred = false;
41 117 QString label;
42 117 GeoPoint geo; // Sprint 103: a pinned position (ADR;GEO="geo:…")
43 117 int sourceIndex = -1;
44 bool isEmpty() const;
45 // "Hauptstr. 1, 10115 Berlin, Germany" — the card shows one line.
46 QString oneLine() const;
47
12/24
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 117 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 117 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 117 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 117 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 117 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 117 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 117 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 117 times.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 117 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 117 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 117 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 117 times.
117 bool operator==(const ContactAddress &) const = default;
48 };
49
50 // BDAY / ANNIVERSARY; a date without a year keeps year 0 (D-2, "Year unknown").
51 struct PartialDate {
52 53312 int year = 0;
53 53056 int month = 0;
54 52960 int day = 0;
55
7/8
✓ Branch 2 → 3 taken 6941 times.
✓ Branch 2 → 7 taken 67627 times.
✓ Branch 3 → 4 taken 6931 times.
✓ Branch 3 → 7 taken 10 times.
✓ Branch 4 → 5 taken 6928 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 6928 times.
✗ Branch 5 → 7 not taken.
74568 bool isValid() const { return month >= 1 && month <= 12 && day >= 1 && day <= 31; }
56 2639 bool hasYear() const { return year > 0; }
57 // "1985-04-12" or "--04-12".
58 QString toIso() const;
59 static PartialDate fromIso(const QString &text);
60
5/6
✓ Branch 2 → 3 taken 256 times.
✓ Branch 2 → 4 taken 53056 times.
✓ Branch 4 → 5 taken 96 times.
✓ Branch 4 → 6 taken 52960 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 52960 times.
53312 bool operator==(const PartialDate &) const = default;
61 };
62
63 // Sprint 103 (D-9): a labelled date (itemN.X-ABDATE + X-ABLabel).
64 struct ContactDate {
65 394 QString label;
66 394 PartialDate date;
67 394 int sourceIndex = -1;
68
4/6
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 394 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 394 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 393 times.
394 bool operator==(const ContactDate &) const = default;
69 };
70
71 // N (RFC 6350 §6.2.2).
72 struct ContactName {
73 26540 QString family;
74 24398 QString given;
75 24299 QString additional;
76 24299 QString prefix;
77 24299 QString suffix;
78 bool isEmpty() const;
79
7/10
✓ Branch 3 → 4 taken 2142 times.
✓ Branch 3 → 5 taken 24398 times.
✓ Branch 6 → 7 taken 99 times.
✓ Branch 6 → 8 taken 24299 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 24299 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 24299 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 24299 times.
26540 bool operator==(const ContactName &) const = default;
80 };
81
82 // PHOTO: inline data, a URI (shown only on the account's own origin, D-15)
83 // or none.
84 struct ContactPhoto {
85 enum class Kind { None, Inline, Uri };
86 26458 Kind kind = Kind::None;
87 26126 QByteArray data; // decoded image bytes (Inline)
88 26125 QString mimeType; // "image/jpeg", "image/png" (best effort)
89 26125 QString uri; // Uri
90
6/8
✓ Branch 2 → 3 taken 332 times.
✓ Branch 2 → 4 taken 26126 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 26125 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 26125 times.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 26125 times.
26458 bool operator==(const ContactPhoto &) const = default;
91 };
92
93 // A known property MailJD shows read-only (PRONOUNS, …): label and
94 // display value.
95 struct ContactExtraField {
96 ✗ QString name; // property name, e.g. "IMPP"
97 ✗ QString label; // "Skype", "spouse" …
98 ✗ QString value;
99 ✗ bool operator==(const ContactExtraField &) const = default;
100 };
101
102 struct ContactCard {
103 6 QString uid;
104 6 QString version; // "2.1", "3.0" or "4.0"
105 6 QString kind = QStringLiteral("individual"); // individual, group, org, location, family (X-MAILJD-KIND)
106 6 QString formattedName; // FN as stored (may be empty)
107 6 ContactName name;
108 6 QString nickname;
109 6 QList<ContactValue> emails;
110 6 QList<ContactValue> phones;
111 6 QList<ContactValue> urls;
112 6 QList<ContactAddress> addresses;
113 6 QString organization; // ORG first component
114 6 QString department; // ORG second component
115 6 QString title;
116 6 PartialDate birthday;
117 6 PartialDate anniversary;
118 6 QString note;
119 6 QStringList categories; // CATEGORIES (Nextcloud groups, D-7)
120 6 QStringList members; // MEMBER / X-ADDRESSBOOKSERVER-MEMBER URIs
121 6 ContactPhoto photo;
122 6 QList<ContactExtraField> extraFields;
123 // Sprint 103 people details (VCardPeople.cpp / VCardPeoplePatch.cpp).
124 6 QList<ContactRelation> relations;
125 5 ContactMet met;
126 5 QList<ContactSocial> social;
127 5 QStringList languages; // LANG in preference order
128 5 QStringList interests; // INTEREST (HOBBY/EXPERTISE read as interests)
129 5 QList<ContactDate> otherDates;
130 5 QString birthplace;
131 5 GeoPoint geo; // card GEO
132 5 QString timeZone; // TZ as stored (read-only)
133 5 QString gender; // GENDER sex component as stored ("F", "Female", …; D-4 labels, F103-41)
134 5 QString genderIdentity; // GENDER identity component (read-only, kept on write)
135 5 QList<ContactMailLink> mailLinks;
136 25198 bool isGroup() const { return kind == QLatin1String("group"); }
137 // A family's own entry: written as X-MAILJD-KIND:family (other clients see
138 // an individual card). Its members are its RELATED member lines; it has no
139 // other relationships.
140 53245 bool isFamily() const { return kind == QLatin1String("family"); }
141
33/64
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 6 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 6 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 6 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 6 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 6 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 6 times.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 6 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 6 times.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 6 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 6 times.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 6 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 6 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 6 times.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 6 times.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 6 times.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 6 times.
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 6 times.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 6 times.
✗ Branch 60 → 61 not taken.
✓ Branch 60 → 62 taken 6 times.
✓ Branch 63 → 64 taken 1 time.
✓ Branch 63 → 65 taken 5 times.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 68 taken 5 times.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 5 times.
✗ Branch 72 → 73 not taken.
✓ Branch 72 → 74 taken 5 times.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 5 times.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 5 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 5 times.
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 86 taken 5 times.
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 89 taken 5 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 5 times.
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 5 times.
✗ Branch 96 → 97 not taken.
✓ Branch 96 → 98 taken 5 times.
6 bool operator==(const ContactCard &) const = default;
142 };
143 Q_DECLARE_METATYPE(ContactCard)
144
145 // An address book as discovered on a server (CardDavClient) and as kept in
146 // the store (S97.1.2/S97.1.3). The local book has accountId "" and path
147 // "local" ("On this computer").
148 struct AddressBookInfo {
149 qint64 id = 0; // store id (0 before it is stored)
150 QString accountId;
151 QString path; // DAV href path
152 QString displayName; // server display name (may be empty)
153 bool readOnly = false; // no write privilege (RFC 3744)
154 QString ctag;
155 QString syncToken; // RFC 6578 token of the last applied sync
156 bool supportsSyncCollection = false;
157 QStringList supportedVersions; // supported-address-data, e.g. "3.0", "4.0"
158 qint64 maxResourceSize = 0; // 0 = not announced
159 bool fetched = false; // one complete fetch done (D-5)
160 QDateTime lastSync;
161 QString lastError; // technical; shown only behind "Details"
162 int skippedCards = 0; // cards of the last sync that could not be read
163 bool showInAll = true; // per-book "Show in All contacts"
164 bool useForCompletion = true; // per-book "Use for address completion"
165 DavPushOffer push; // Sprint 96: read with the book's properties; not stored
166
3/4
✓ Branch 3 → 4 taken 10387 times.
✓ Branch 3 → 8 taken 3415 times.
✓ Branch 6 → 7 taken 10387 times.
✗ Branch 6 → 8 not taken.
13802 bool isLocal() const { return accountId.isEmpty() && path == QLatin1String("local"); }
167 };
168
4/7
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 14 not taken.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 14 → 15 not taken.
1 Q_DECLARE_METATYPE(AddressBookInfo)
169
170 // One stored card: the raw vCard plus its server identity and sync state.
171 struct StoredCard {
172 qint64 id = 0;
173 qint64 bookId = 0;
174 QString uid;
175 QString href; // resource URL or path on the server ("" for local cards)
176 QString etag; // ETag of base_raw
177 QString raw; // the card as MailJD shows it (local edits applied)
178 QString baseRaw; // the card as last seen on the server (merge base)
179 QString syncState; // "", "pending", "conflict", "legacy" (read-only until refetched)
180 bool writable = true;
181 };
182
183 // Write-back queue entry of one card (S97.1.4, the CalendarOutbox contract).
184 struct ContactOutboxEntry {
185 qint64 id = 0;
186 qint64 bookId = 0;
187 QString uid;
188 QString operation; // "put" or "delete"
189 QString href;
190 QString baseEtag; // empty = create (If-None-Match: *)
191 int revision = 1; // bumped by every local change while queued
192 int attempts = 0;
193 qint64 nextAttempt = 0; // epoch seconds
194 QString lastError;
195 QString state = QStringLiteral("pending"); // "pending" or "conflict"
196 QString serverRaw; // conflict: the server version
197 QString serverEtag;
198 QStringList conflictFields;
199 qint64 dependsOn = 0; // outbox id that must finish first (move: delete after create)
200 QString baseRaw; // move source (D-22): the card's last server version
201 };
202