summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--CHANGELOG.md7
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md64
-rw-r--r--src/htmlbuilder.cpp103
-rw-r--r--src/htmlbuilder.h40
-rw-r--r--src/messageview.cpp16
-rw-r--r--src/messageview.h8
-rw-r--r--tests/test_htmlbuilder.cpp121
7 files changed, 340 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94ae6c9..ef2b5be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,13 @@ point at which they are stable.
### Fixed
+- **The message pane follows the desktop theme.** Its stylesheet hardcoded
+ light-theme greys and set no background at all, so plain-text mail rendered
+ as black on white inside a dark window. The colours now derive from the
+ palette, with the secondary ones blended from it rather than fixed, since a
+ grey chosen to read as subtle on white is nearly invisible on near-black. A
+ message that brings its own HTML still brings its own colours: that styling
+ is deliberately left alone.
- **A message whose HTML body carries a `Content-Id` renders**, instead of
opening blank with the app reporting no HTML part. A content id makes a part
referenceable, not undisplayable, and setting one on the body is legal and
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
index 2bcdd0a..faf510a 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
@@ -60,6 +60,7 @@ taking that too literally.
| 9 | No in-app view of configured shortcuts | discoverability | S | **done** |
| 10 | Reaching an account's inbox takes two steps | workflow | S | **postponed** (partly done) |
| 11 | Icon, `.desktop` file, SlackBuild | packaging | M | **done** |
+| 12 | Message pane is light-theme only | presentation | S | **done** |
| 13 | No visual feedback that an action stuck | feedback | S | **done** |
| 14 | Tag column unreadable, tags need another home | presentation | M | **done** |
| 15 | Attachments are parsed but unreachable from the UI | information | M | **done** |
@@ -433,10 +434,12 @@ unchanged. Regression test in `test_config`.
**Caution:** do not hardcode colors. The app should follow the desktop palette;
a hand-picked grey that looks right on a light theme is unreadable on a dark
-one. Use `QPalette` roles. This applies to `HtmlBuilder`'s CSS too, which
-currently hardcodes `#bbb`, `#555`, `#000`, `#666`, `#ddd` and no background,
-and will look wrong under a dark theme. That is arguably its own item; see
-item 12 below if it gets split out.
+one. Use `QPalette` roles. This applied to `HtmlBuilder`'s CSS too, which was
+split out as item 12 and **done on 2026-08-07**: its colours now derive from
+the palette, with the secondary ones blended rather than fixed. The same rule
+governs whatever this item adds to the thread list, and item 12's test, which
+asserts that no colour appears that the palette did not supply, is the pattern
+to copy.
### Refined by the user, 2026-08-04
@@ -623,6 +626,57 @@ Packaging, independent of everything above, and can proceed in parallel.
---
+## 12. Message pane is light-theme only
+
+**Split from item 5**, which recorded the rule against hardcoded colours.
+Listed in the deferred table until it was picked up on 2026-08-07.
+
+**Observed:** the user runs a dark desktop (`color-scheme: prefer-dark`), and
+plain-text mail rendered as black on white inside a dark window.
+
+**Cause (verified in code):** `kStyle` in `src/htmlbuilder.cpp` hardcoded
+`#bbb`, `#555`, `#000`, `#666`, `#ddd` and `#4a6f8a`, and set **no background
+at all**, so the web view's own default showed through whatever the desktop
+was.
+
+**Approach as built.** A `HtmlBuilder::Palette` struct passed into
+`build`/`buildThread`, derived from a `QPalette` by `paletteFrom()`. Passed in
+rather than read from `qApp` inside the builder, so the stylesheet can be
+tested against a known palette with no running application.
+
+- **`Base` and `Text`, not `Window` and `WindowText`.** The pane is a content
+ surface like a text edit, and on many themes `Base` differs from `Window`.
+- **The derived colours are blends, not fixed greys.** This is the part that
+ makes it work both ways round: a `#555` chosen to read as "subtle" on white
+ is nearly invisible on `#2b2b2b`. `dim` and `border` are mixes of text and
+ background, so they land at the right contrast whichever way the theme goes.
+- The quote colour keeps its hue, since "this is quoted" is carried by being a
+ different colour rather than a dimmer one, but it is pulled toward the
+ background so it stays readable rather than glowing on dark.
+
+Measured on the user's actual theme: background `#2b2b2b`, text `#dedede`, dim
+`#969696`, border `#585858`, quote `#6490b0`.
+
+**Scope, and it is asserted in a test so it cannot drift.** A message that
+brings its own HTML brings its own colours, and those are left alone.
+Rewriting a sender's styling would break layouts that depend on it, and a
+newsletter that sets a white background is entitled to stay white. This item
+themes the plain-text render and the chrome around messages, nothing else. So
+HTML-heavy mail will still look light, correctly.
+
+**`MessageView` passes its own widget palette**, not the application's: a style
+sheet or a themed parent can give the pane different colours from `qApp`. It
+also re-renders on `QEvent::PaletteChange`, because the document's colours are
+baked into its stylesheet at build time and it does not restyle itself the way
+a widget does; without that, switching the desktop theme would leave the open
+thread on the old palette until the next selection.
+
+**Verification.** The load-bearing test asserts the **negative**: no hex colour
+appears in the `<style>` block that the palette did not supply. A test that
+only checks the palette's colours are present passes with a leftover literal
+still in place, and a single leftover literal is the entire defect. Confirmed
+by mutation: putting one hardcoded colour back fails it.
+
## 13. No visual feedback that an action stuck
**Observed:** selecting a thread and hitting Delete changed nothing on screen.
@@ -2497,7 +2551,7 @@ sequence, appended as they arise.
| # | Item | Why here |
|---|------|----------|
-| 12 | `HtmlBuilder` CSS is light-theme only | Split from item 5. Hardcoded greys and no background color; a dark desktop theme will render message bodies badly. Fix likely means passing palette-derived colors into the CSS, which affects `HtmlBuilder`'s tests. |
+| 12 | `HtmlBuilder` CSS is light-theme only | **Done 2026-08-07**, and moved to the main status table. Kept listed here so the split from item 5 stays traceable. |
## Adding to this document
diff --git a/src/htmlbuilder.cpp b/src/htmlbuilder.cpp
index ccb9171..c8f039f 100644
--- a/src/htmlbuilder.cpp
+++ b/src/htmlbuilder.cpp
@@ -18,25 +18,92 @@
#include "htmlbuilder.h"
+#include <QCoreApplication>
+#include <QGuiApplication>
#include <QRegularExpression>
namespace {
-const char *kStyle = R"CSS(
-body { font-family: sans-serif; font-size: 10pt; margin: 12px; }
+/// The stylesheet, with every colour supplied by the caller.
+///
+/// %1 background, %2 text, %3 quote, %4 border, %5 dim.
+const char *kStyleTemplate = R"CSS(
+body { font-family: sans-serif; font-size: 10pt; margin: 12px;
+ background: %1; color: %2; }
pre.plain { white-space: pre-wrap; word-wrap: break-word;
font-family: monospace; margin: 0; }
-span.quote { color: #4a6f8a; }
-.message { border-top: 1px solid #bbb; padding: 10px 0; }
+span.quote { color: %3; }
+.message { border-top: 1px solid %4; padding: 10px 0; }
.message:first-child { border-top: none; }
-.msg-header { font-size: 9pt; color: #555; margin-bottom: 8px; }
-.msg-header .who { font-weight: bold; color: #000; }
-.stub { font-size: 9pt; color: #666; padding: 4px 0;
- border-top: 1px solid #ddd; }
+.msg-header { font-size: 9pt; color: %5; margin-bottom: 8px; }
+.msg-header .who { font-weight: bold; color: %2; }
+.stub { font-size: 9pt; color: %5; padding: 4px 0;
+ border-top: 1px solid %4; }
)CSS";
+/// Mixes two colours, `weight` being how much of `a` survives.
+///
+/// Blending is what makes the derived colours theme-correct. A fixed grey is
+/// only "subtle" against the background it was chosen for: #555 reads as a
+/// quiet label on white and nearly vanishes on near-black.
+QColor blend(const QColor &a, const QColor &b, qreal weight)
+{
+ const qreal inverse = 1.0 - weight;
+ return QColor::fromRgbF(a.redF() * weight + b.redF() * inverse,
+ a.greenF() * weight + b.greenF() * inverse,
+ a.blueF() * weight + b.blueF() * inverse);
+}
+
} // namespace
+HtmlBuilder::Palette HtmlBuilder::paletteFrom(const QPalette &palette)
+{
+ // Base and Text, not Window and WindowText: the pane is a content surface
+ // like a text edit, and on many themes Base differs from Window.
+ const QColor background = palette.color(QPalette::Base);
+ const QColor text = palette.color(QPalette::Text);
+
+ Palette result;
+ result.background = background;
+ result.text = text;
+
+ // Both derived from the pair, so they land at the right contrast whichever
+ // way round the theme is.
+ result.dim = blend(text, background, 0.6);
+ result.border = blend(text, background, 0.25);
+
+ // The quote colour keeps its hue, since "this is quoted" is carried by the
+ // colour being different rather than by it being dimmer, but it is pulled
+ // toward the background so it stays readable on a dark theme instead of
+ // glowing.
+ const QColor quoteHue(0x4a, 0x6f, 0x8a);
+ result.quote = background.lightnessF() < 0.5
+ ? blend(quoteHue.lighter(160), background, 0.75)
+ : blend(quoteHue, background, 0.85);
+
+ return result;
+}
+
+HtmlBuilder::Palette HtmlBuilder::defaultPalette()
+{
+ if (const QGuiApplication *app =
+ qobject_cast<QGuiApplication *>(QCoreApplication::instance()))
+ return paletteFrom(app->palette());
+
+ // No GUI application: only reachable from a test that did not pass a
+ // palette. A default-constructed QPalette is light, which matches what
+ // this code did before it was themed at all.
+ return paletteFrom(QPalette());
+}
+
+QString HtmlBuilder::styleSheet(const Palette &palette)
+{
+ return QString::fromUtf8(kStyleTemplate)
+ .arg(palette.background.name(), palette.text.name(),
+ palette.quote.name(), palette.border.name(),
+ palette.dim.name());
+}
+
QString HtmlBuilder::renderPlain(const QString &text)
{
QString out;
@@ -61,12 +128,12 @@ QString HtmlBuilder::renderPlain(const QString &text)
return out;
}
-QString HtmlBuilder::document(const QString &bodyHtml)
+QString HtmlBuilder::document(const QString &bodyHtml, const Palette &palette)
{
return QStringLiteral(
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
"<style>%1</style></head><body>%2</body></html>")
- .arg(QString::fromUtf8(kStyle), bodyHtml);
+ .arg(styleSheet(palette), bodyHtml);
}
QString HtmlBuilder::namespaceCids(const QString &html, const QString &prefix)
@@ -218,14 +285,26 @@ QString HtmlBuilder::renderStub(const ParsedMessage &message)
QString HtmlBuilder::build(const ParsedMessage &message, Mode mode)
{
+ return build(message, mode, defaultPalette());
+}
+
+QString HtmlBuilder::build(const ParsedMessage &message, Mode mode,
+ const Palette &palette)
+{
ThreadRenderItem item;
item.message = message;
item.expanded = true;
- return document(renderBody(item, mode));
+ return document(renderBody(item, mode), palette);
}
QString HtmlBuilder::buildThread(const QList<ThreadRenderItem> &items, Mode mode)
{
+ return buildThread(items, mode, defaultPalette());
+}
+
+QString HtmlBuilder::buildThread(const QList<ThreadRenderItem> &items, Mode mode,
+ const Palette &palette)
+{
QString body;
for (int i = 0; i < items.size(); ++i) {
@@ -246,5 +325,5 @@ QString HtmlBuilder::buildThread(const QList<ThreadRenderItem> &items, Mode mode
renderBody(item, mode));
}
- return document(body);
+ return document(body, palette);
}
diff --git a/src/htmlbuilder.h b/src/htmlbuilder.h
index 6e306dd..75fd1f8 100644
--- a/src/htmlbuilder.h
+++ b/src/htmlbuilder.h
@@ -18,7 +18,9 @@
#pragma once
+#include <QColor>
#include <QList>
+#include <QPalette>
#include "mimeparser.h"
@@ -60,11 +62,46 @@ public:
ForcePlain, ///< Always render the plain part, escaped.
};
+ /// The colours the document's own stylesheet uses.
+ ///
+ /// Passed in rather than read from qApp inside the builder, so the CSS can
+ /// be tested against a known palette without a running application, and so
+ /// nothing here depends on widget state.
+ ///
+ /// **Scope.** These style the chrome around messages and the plain-text
+ /// render. A message that brings its own HTML brings its own colours, and
+ /// those are deliberately left alone: rewriting a sender's styling would
+ /// break layouts that depend on it, and a newsletter that sets a white
+ /// background is entitled to stay white.
+ struct Palette {
+ QColor background; ///< The pane itself.
+ QColor text; ///< Body text.
+ QColor dim; ///< Headers and stubs: present but secondary.
+ QColor border; ///< Rules between messages.
+ QColor quote; ///< Quoted lines in plain text.
+ };
+
+ /// Derives the document palette from a widget palette.
+ ///
+ /// The dim and border colours are blends rather than fixed greys, which is
+ /// what makes this work on a dark theme: a hardcoded #555 that reads as
+ /// "subtle" on white is nearly invisible on near-black.
+ static Palette paletteFrom(const QPalette &palette);
+
+ /// The palette used when a caller supplies none: the running application's.
+ /// Falls back to a light default with no QApplication, which only happens
+ /// in a test that did not ask for a palette.
+ static Palette defaultPalette();
+
/// Single message, used for the error card and for tests.
static QString build(const ParsedMessage &message, Mode mode);
+ static QString build(const ParsedMessage &message, Mode mode,
+ const Palette &palette);
/// The whole thread, oldest first.
static QString buildThread(const QList<ThreadRenderItem> &items, Mode mode);
+ static QString buildThread(const QList<ThreadRenderItem> &items, Mode mode,
+ const Palette &palette);
/// Rewrites cid: URLs in an HTML body to their namespaced form.
static QString namespaceCids(const QString &html, const QString &prefix);
@@ -73,5 +110,6 @@ private:
static QString renderPlain(const QString &text);
static QString renderBody(const ThreadRenderItem &item, Mode mode);
static QString renderStub(const ParsedMessage &message);
- static QString document(const QString &bodyHtml);
+ static QString document(const QString &bodyHtml, const Palette &palette);
+ static QString styleSheet(const Palette &palette);
};
diff --git a/src/messageview.cpp b/src/messageview.cpp
index d7167bf..10b21a9 100644
--- a/src/messageview.cpp
+++ b/src/messageview.cpp
@@ -378,12 +378,26 @@ void MessageView::showDetailsDialog()
dialog.exec();
}
+void MessageView::changeEvent(QEvent *event)
+{
+ QWidget::changeEvent(event);
+
+ // Only when there is something to re-render: rendering an empty item list
+ // would replace a deliberately blank pane with an empty document.
+ if (event->type() == QEvent::PaletteChange && !m_items.isEmpty())
+ render();
+}
+
void MessageView::render()
{
const HtmlBuilder::Mode mode =
m_preferHtml ? HtmlBuilder::PreferHtml : HtmlBuilder::ForcePlain;
- setDocument(HtmlBuilder::buildThread(m_items, mode));
+ // This widget's palette, not the application's: a style sheet or a themed
+ // parent can give the pane different colours from qApp, and the document
+ // has to match the frame it sits in rather than the app default.
+ setDocument(HtmlBuilder::buildThread(m_items, mode,
+ HtmlBuilder::paletteFrom(palette())));
rebuildAttachmentBar();
// Blocking is discovered during load, so check shortly afterwards.
diff --git a/src/messageview.h b/src/messageview.h
index 1be17f8..c55f5c5 100644
--- a/src/messageview.h
+++ b/src/messageview.h
@@ -105,6 +105,14 @@ protected:
/// rather than one widget.
bool eventFilter(QObject *watched, QEvent *event) override;
+ /// Re-renders when the desktop theme changes.
+ ///
+ /// The document's colours are baked into its stylesheet at build time, so
+ /// unlike a widget it does not restyle itself: switching the desktop from
+ /// light to dark would otherwise leave the open thread on the old palette
+ /// until the next selection.
+ void changeEvent(QEvent *event) override;
+
private:
void render();
void updateHeader();
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"