aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-07 12:12:18 +0200
committerDanilo M. <danix@danix.xyz>2026-08-07 12:12:18 +0200
commit3826759d5167fb7a6f9449f9a39814c75771f449 (patch)
tree9fb1bc09bb89bd2c77a7c8c46247c577d8666cfd /tests
parent5a4d8f5f021dc98b2a7cc471125aa3040c02675c (diff)
downloadqtmaildir-3826759d5167fb7a6f9449f9a39814c75771f449.tar.gz
qtmaildir-3826759d5167fb7a6f9449f9a39814c75771f449.zip
fix(ui): the message pane follows the desktop theme
The stylesheet hardcoded #bbb, #555, #000, #666, #ddd and #4a6f8a, and set no background at all, so on a dark desktop plain-text mail rendered as black on white inside a dark window and the web view's own default showed through. The colours now come from a Palette struct derived from QPalette, and are passed into the builder rather than read from qApp inside it, so the stylesheet can be tested against a known palette with no running application. Base and Text rather than Window and WindowText: the pane is a content surface like a text edit, and on many themes those differ. The secondary colours are blends of text and background, not fixed greys. That is the part that makes it work both ways round, since a #555 chosen to read as subtle on white is nearly invisible on #2b2b2b. The quote colour keeps its hue, because "this is quoted" is carried by being a different colour rather than a dimmer one, but is pulled toward the background so it stays readable instead of glowing on dark. A sender's own HTML is deliberately left alone, and a test asserts that so it cannot drift: rewriting a sender's styling would break layouts that depend on it, and a newsletter setting a white background is entitled to stay white. This themes the plain-text render and the chrome around messages, nothing more. MessageView passes its own widget palette rather than the application's, since a style sheet or a themed parent can give the pane different colours from qApp, and re-renders on PaletteChange: the document's colours are baked into its stylesheet at build time, so unlike a widget it does not restyle itself when the desktop theme changes. The load-bearing test asserts the negative, that no hex colour appears in the style block which the palette did not supply. A test checking only that the palette's colours are present passes with a leftover literal still there, and one leftover literal is the whole defect. Confirmed by mutation. Closes item 12. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/test_htmlbuilder.cpp121
1 files changed, 121 insertions, 0 deletions
diff --git a/tests/test_htmlbuilder.cpp b/tests/test_htmlbuilder.cpp
index 04bd294..7fa45a2 100644
--- a/tests/test_htmlbuilder.cpp
+++ b/tests/test_htmlbuilder.cpp
@@ -16,6 +16,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <QPalette>
+#include <QRegularExpression>
#include <QSet>
#include <QtTest>
#include "cidschemehandler.h"
@@ -42,6 +44,12 @@ private slots:
void namespacesMultipleCidRefsOnOneLine();
void namespacesWhitespaceAroundEquals();
void namespacedKeyRejectsPrefixContainingSeparator();
+
+ // Theming.
+ void aDarkPaletteProducesADarkDocument();
+ void everyColourComesFromThePalette();
+ void theBodyAlwaysGetsABackground();
+ void aSendersOwnHtmlIsNotRecoloured();
};
void TestHtmlBuilder::escapesPlainText()
@@ -276,5 +284,118 @@ void TestHtmlBuilder::namespacedKeyRejectsPrefixContainingSeparator()
QVERIFY(html.contains(QStringLiteral("cid:%1").arg(expectedKey)));
}
+/// A palette with unmistakable colours, so a hardcoded value cannot pass by
+/// coincidentally resembling a real theme.
+static QPalette makeTestPalette(const QColor &window, const QColor &text)
+{
+ QPalette palette;
+ palette.setColor(QPalette::Base, window);
+ palette.setColor(QPalette::Window, window);
+ palette.setColor(QPalette::Text, text);
+ palette.setColor(QPalette::WindowText, text);
+ return palette;
+}
+
+void TestHtmlBuilder::aDarkPaletteProducesADarkDocument()
+{
+ // The whole point of the item: the CSS was hardcoded light, so a user on a
+ // dark desktop read plain-text mail as black on white inside a dark window.
+ ParsedMessage msg;
+ msg.plainBody = QStringLiteral("hello");
+
+ const HtmlBuilder::Palette dark = HtmlBuilder::paletteFrom(
+ makeTestPalette(QColor(0x12, 0x34, 0x56), QColor(0xab, 0xcd, 0xef)));
+
+ const QString html =
+ HtmlBuilder::build(msg, HtmlBuilder::ForcePlain, dark);
+
+ QVERIFY2(html.contains(QStringLiteral("#123456")), qPrintable(html));
+ QVERIFY2(html.contains(QStringLiteral("#abcdef")), qPrintable(html));
+}
+
+void TestHtmlBuilder::everyColourComesFromThePalette()
+{
+ // A single leftover literal is the whole defect, and it survives a test
+ // that only checks the palette colours are present. Assert the negative:
+ // no hex colour appears that the palette did not put there.
+ ParsedMessage msg;
+ msg.plainBody = QStringLiteral("> quoted\nplain");
+
+ const HtmlBuilder::Palette dark = HtmlBuilder::paletteFrom(
+ makeTestPalette(QColor(0x12, 0x34, 0x56), QColor(0xab, 0xcd, 0xef)));
+ const QString html =
+ HtmlBuilder::build(msg, HtmlBuilder::ForcePlain, dark);
+
+ // Only the <style> block: a sender's own HTML is not ours to police, and
+ // the body of this message carries no colours anyway.
+ const qsizetype start = html.indexOf(QStringLiteral("<style>"));
+ const qsizetype end = html.indexOf(QStringLiteral("</style>"));
+ QVERIFY(start >= 0 && end > start);
+ const QString style = html.mid(start, end - start);
+
+ static const QRegularExpression hex(QStringLiteral("#[0-9a-fA-F]{3,8}\\b"));
+ auto it = hex.globalMatch(style);
+ QSet<QString> found;
+ while (it.hasNext())
+ found.insert(it.next().captured(0).toLower());
+
+ // Every colour in the stylesheet must be one the palette supplied. The
+ // derived ones (a border, a dimmed label) are blends of those, so they are
+ // listed by the builder rather than being free-floating literals.
+ const QSet<QString> allowed = {
+ dark.background.name().toLower(), dark.text.name().toLower(),
+ dark.dim.name().toLower(), dark.border.name().toLower(),
+ dark.quote.name().toLower(),
+ };
+
+ for (const QString &colour : found) {
+ QVERIFY2(allowed.contains(colour),
+ qPrintable(QStringLiteral("stylesheet carries '%1', which the "
+ "palette did not supply: a "
+ "hardcoded colour survives")
+ .arg(colour)));
+ }
+}
+
+void TestHtmlBuilder::theBodyAlwaysGetsABackground()
+{
+ // The original CSS set no background at all, which is why the pane was
+ // white: the web view's default showed through regardless of the desktop.
+ ParsedMessage msg;
+ msg.plainBody = QStringLiteral("hello");
+
+ const HtmlBuilder::Palette dark = HtmlBuilder::paletteFrom(
+ makeTestPalette(QColor(0x12, 0x34, 0x56), QColor(0xab, 0xcd, 0xef)));
+ const QString html =
+ HtmlBuilder::build(msg, HtmlBuilder::ForcePlain, dark);
+
+ const qsizetype start = html.indexOf(QStringLiteral("<style>"));
+ const qsizetype end = html.indexOf(QStringLiteral("</style>"));
+ const QString style = html.mid(start, end - start);
+
+ static const QRegularExpression bodyRule(
+ QStringLiteral("body\\s*\\{[^}]*background[^}]*\\}"));
+ QVERIFY2(bodyRule.match(style).hasMatch(),
+ qPrintable(QStringLiteral("body has no background rule:\n") + style));
+}
+
+void TestHtmlBuilder::aSendersOwnHtmlIsNotRecoloured()
+{
+ // Scope, asserted so it does not drift: an HTML message brings its own
+ // styling and this change must not start rewriting it. A newsletter that
+ // sets its own white background stays white, and that is correct.
+ ParsedMessage msg;
+ msg.htmlBody = QStringLiteral(
+ "<div style=\"background:#ffffff;color:#000000\">hi</div>");
+
+ const HtmlBuilder::Palette dark = HtmlBuilder::paletteFrom(
+ makeTestPalette(QColor(0x12, 0x34, 0x56), QColor(0xab, 0xcd, 0xef)));
+ const QString html =
+ HtmlBuilder::build(msg, HtmlBuilder::PreferHtml, dark);
+
+ QVERIFY(html.contains(QStringLiteral("background:#ffffff")));
+ QVERIFY(html.contains(QStringLiteral("color:#000000")));
+}
+
QTEST_MAIN(TestHtmlBuilder)
#include "test_htmlbuilder.moc"