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: 100.0% 26 / 0 / 26
Functions: 100.0% 24 / 0 / 24
Branches: 70.0% 14 / 0 / 20

presentation/MailReaderState.h
Line Branch Exec Source
1 #pragma once
2 #include "util/SpamHeaders.h"
3 #include "controller/MailDisplayPort.h"
4 #include <QVariantMap>
5 #include <QLocale>
6 #include <QPointer>
7 #include <QQuickTextDocument>
8 #include <QTimer>
9 #include <memory>
10 #include "service/NativeMailDocument.h"
11
12 class NativeDocumentService;
13 class NativeResourceDocument;
14
15 // A single current reader document, owned outside the scene tree. Logical tabs
16 // retain identities, not copies of this body. Native preparation is shared and
17 // lazy; native modes never construct WebEngine or a second parser/cache.
18 class MailReaderState final : public QObject, public MailDisplayPort {
19 77 Q_OBJECT
20 Q_PROPERTY(QVariantMap identity READ identity NOTIFY changed)
21 Q_PROPERTY(QString generation READ generation NOTIFY changed)
22 Q_PROPERTY(QString subject READ subject NOTIFY changed)
23 Q_PROPERTY(QString sender READ sender NOTIFY changed)
24 Q_PROPERTY(QString recipient READ recipient NOTIFY changed)
25 // Sprint 97 S97.5.1: every Cc address (cache schema v24; "" before).
26 Q_PROPERTY(QString cc READ cc NOTIFY changed)
27 Q_PROPERTY(QString dateText READ dateText NOTIFY changed)
28 // Sprint 103 S103.6.3 (D-15): what a mail link stores (Message-ID without
29 // angle brackets, the day "YYYY-MM-DD").
30 Q_PROPERTY(QString messageId READ messageId NOTIFY changed)
31 Q_PROPERTY(QString dateIso READ dateIso NOTIFY changed)
32 Q_PROPERTY(QString plainText READ plainText NOTIFY changed)
33 Q_PROPERTY(QString error READ error NOTIFY changed)
34 Q_PROPERTY(bool retryable READ retryable NOTIFY changed)
35 Q_PROPERTY(QString status READ status NOTIFY changed)
36 Q_PROPERTY(QString readingMode READ readingMode NOTIFY changed)
37 Q_PROPERTY(QString effectiveMode READ effectiveMode NOTIFY changed)
38 Q_PROPERTY(QString notice READ notice NOTIFY changed)
39 Q_PROPERTY(QVariantList attachments READ attachments NOTIFY changed)
40 // F100-02: hosts the HTML part loads images or styles from ("" mail has
41 // none). HTML-light and Text never load them; the reader offers to.
42 Q_PROPERTY(QStringList remoteHosts READ remoteHosts NOTIFY changed)
43 // F100-56: the server spam filter's verdict {known, spam, score, threshold,
44 // tool, tests, text}; known false when no filter wrote a header.
45 Q_PROPERTY(QVariantMap spam READ spam NOTIFY changed)
46 Q_PROPERTY(QObject *htmlRenderer READ htmlRenderer CONSTANT)
47 public:
48 explicit MailReaderState(QObject *parent = nullptr, NativeDocumentService *service = nullptr);
49 ~MailReaderState() override;
50 794 QObject &displayObject() override { return *this; }
51 794 QMetaObject::Connection subscribeRetry(QObject *receiver, std::function<void()> retry) override {
52 794 return connect(this, &MailReaderState::retryRequested, receiver, std::move(retry));
53 }
54 void presentMail(const MailId &, quint64, const MailHeader &, const MailBody &) override;
55 void presentLoading(const MailId &, quint64, const MailHeader &, const QString &, bool) override;
56 void presentError(const MailId &, quint64, const MailHeader &, const QString &, bool) override;
57 void presentBodyVariant(const MailId &identity, const MailBody &variant) override;
58 void clearDisplay() override;
59
1/2
✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 4 not taken.
8 Q_INVOKABLE void retry() { if (m_retryable) emit retryRequested(); }
60 QVariantMap identity() const;
61 36 QString generation() const { return QString::number(m_generation); }
62 4177 QString subject() const { return m_header.subject; }
63 24561 QString sender() const { return m_header.from; }
64 17978 QString recipient() const { return m_header.to; }
65 7942 QString cc() const { return m_header.cc; }
66 3976 QString messageId() const { return m_header.messageId; }
67
5/9
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 6 → 8 taken 3 times.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 9 → 11 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
3 QString dateIso() const { return m_header.date.isValid() ? m_header.date.toLocalTime().date().toString(Qt::ISODate) : QString(); }
68 8124 QString dateText() const {
69
3/3
✓ Branch 2 → 3 taken 8124 times.
✓ Branch 3 → 4 taken 8124 times.
✓ Branch 4 → 5 taken 8124 times.
8124 return QLocale().toString(m_header.date.toLocalTime(), QLocale::ShortFormat);
70 }
71 QString plainText() const;
72 21 QString error() const { return m_error; }
73 34 bool retryable() const { return m_retryable; }
74 5 const MailBody &body() const { return m_body; }
75 31218 QString status() const { return m_status; }
76 // The delayed "Loading message…" text is shown (P90.11 placeholder gate).
77 bool loadingPlaceholderActive() const { return m_loadingPlaceholderActive; }
78 3 QString readingMode() const { return m_mode; }
79 4017 QString effectiveMode() const { return m_effectiveMode; }
80 4 QString notice() const { return m_notice; }
81 // Downloadable attachments of the displayed mail (metadata only, same
82 // visibility rule as the Widgets attachment bar).
83 QVariantList attachments() const;
84 QStringList remoteHosts() const;
85 QVariantMap spam() const;
86 void presentSpamVerdict(const MailId &identity, const MailHeader &header) override;
87 static QVariantMap spamOf(const QString &spamHeaderLines);
88 static QVariantMap spamOfVerdict(const SpamHeaders::Verdict &verdict);
89 // Hosts of http(s) and protocol-relative URLs in src, srcset,
90 // background, poster and CSS url(); lower case, at most 20. Pure.
91 static QStringList remoteHostsOf(const QString &html);
92 767 QObject *htmlRenderer() const { return m_htmlRenderer; }
93 // The host binds this once before loading its QML scene, not on status changes.
94 1590 void setHtmlRenderer(QObject *renderer) { m_htmlRenderer = renderer; }
95 25765 quint64 renderRevision() const { return m_renderGeneration; }
96
5/6
✓ Branch 2 → 3 taken 784 times.
✓ Branch 2 → 7 taken 6871 times.
✓ Branch 3 → 4 taken 614 times.
✓ Branch 3 → 7 taken 170 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 614 times.
7655 bool htmlRequested() const { return m_bodyReady && m_visible && m_effectiveMode == "html"; }
97 // P90.20: true while further plain-text slices still wait to be appended.
98 729 bool plainLayoutPending() const { return m_plainShown < m_plainLimit; }
99 void finishHtml(quint64 revision, const QString &error);
100 void prepareHtml(quint64 revision);
101 public slots:
102 void setCachePath(const QString &path);
103 void setReadingMode(const QString &mode);
104 Q_INVOKABLE void attachDocument(QQuickTextDocument *target);
105 Q_INVOKABLE void setVisible(bool visible);
106 signals:
107 void changed();
108 void retryRequested();
109 // P90.8: the active reading mode needs a body variant that a mode
110 // projection did not load (emptiness is not authoritative). The host
111 // answers with a targeted variant fetch that merges into this body —
112 // no new selection generation.
113 void reprojectionRequested(bool needHtml);
114 private:
115 // Plain-text characters laid out at most (GUI-thread layout, ~0.4 µs per
116 // character measured: 256 Ki ≈ 110 ms instead of 11 s for 50 MiB).
117 static constexpr qsizetype MaxShownPlainChars = 256 * 1024;
118 // P90.20: plain text is laid out in slices of this size, one per event-loop
119 // turn, so no single GUI slice lays out more than ~16 Ki characters.
120 static constexpr qsizetype PlainSliceChars = 16 * 1024;
121 // Documents from this size on render only their visible part.
122 static constexpr qsizetype CulledDocumentChars = 16 * 1024;
123 void applyViewportCulling(qsizetype characters);
124 void appendPlainSlice();
125 void cancelPreparation();
126 void deferPreparationPlaceholder();
127 void render();
128 void replaceDocument(const QString &text, const NativeMailDocument *prepared = nullptr);
129 MailId m_identity;
130 quint64 m_generation = 0;
131 MailHeader m_header;
132 MailBody m_body;
133 QString m_error;
134 bool m_retryable = false;
135 QString m_notice, m_cachePath;
136 QString m_status = QStringLiteral("empty");
137 QString m_mode = QStringLiteral("text"), m_effectiveMode = QStringLiteral("text");
138 QPointer<NativeDocumentService> m_service;
139 QPointer<QQuickTextDocument> m_target;
140 std::unique_ptr<NativeResourceDocument> m_document;
141 // A neutral document has no mail content or image capabilities and can be
142 // kept across rapid selections while the display read is still pending.
143 bool m_documentIsNeutral = true;
144 quint64 m_renderGeneration = 0;
145 bool m_visible = true;
146 bool m_bodyReady = false;
147 bool m_needsPreparation = false;
148 // P90.11: true while the delayed loading placeholder is the visible body.
149 // Internal render/sanitize phases then keep the document stable instead
150 // of swapping in a second placeholder text for the same selection.
151 bool m_loadingPlaceholderActive = false;
152 QTimer m_preparePlaceholderTimer;
153 // Characters of m_body.textPlain in the document / to be shown in total.
154 qsizetype m_plainShown = 0, m_plainLimit = 0;
155 QTimer m_plainSliceTimer;
156 QPointer<QObject> m_htmlRenderer;
157 // remoteHosts() of the shown body, computed once per message and variant.
158 mutable QStringList m_remoteHosts;
159 mutable QString m_remoteHostsKey;
160 };
161