aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_mainwindow.cpp55
-rw-r--r--tests/test_threadlistmodel.cpp86
2 files changed, 140 insertions, 1 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 1fdeaf2..1af95ed 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -81,6 +81,7 @@ private slots:
void aSkippedLocalSyncStillReportsTheOtherRunFinishing();
void anUnobservableLockTableLeavesTheSyncButtonUsable();
void theStatusBarFollowsTheSyncPhase();
+ void aSelectedReadThreadIsNotDimmedIntoTheHighlight();
void markAllReadIsDisabledUntilTheQueryFinishes();
void markAllReadActsOnEveryRowAndUndoesInOneStep();
void markAllReadDoesNothingWhenNothingIsUnread();
@@ -370,6 +371,60 @@ static ThreadSummary makeThread(const QString &id, const QStringList &tags)
return thread;
}
+void TestMainWindow::aSelectedReadThreadIsNotDimmedIntoTheHighlight()
+{
+ // Read threads carry a dimmed Qt::ForegroundRole, blended against the
+ // UNSELECTED background. Qt's own painting prefers a model foreground over
+ // HighlightedText, so without SubjectDelegate::initStyleOption reversing
+ // that, selecting a read row paints it grey on the selection colour, which
+ // is close to unreadable. Seen in a screenshot before it was caught here.
+ //
+ // Rendered rather than asserted on roles: the model is right either way,
+ // and the defect lives entirely in how the delegate resolves them.
+ const Config config;
+ MainWindow window(config);
+
+ auto *model = window.findChild<ThreadListModel *>();
+ QVERIFY(model);
+ auto *view = window.findChild<QTableView *>();
+ QVERIFY(view);
+
+ // Identical but for the unread tag, so any pixel difference between the
+ // two selected rows is the dimming leaking through.
+ ThreadSummary read = makeThread(QStringLiteral("t1"), {});
+ ThreadSummary unread =
+ makeThread(QStringLiteral("t2"), { QStringLiteral("unread") });
+ read.subject = unread.subject = QStringLiteral("Same subject both rows");
+ read.authors = unread.authors = QStringLiteral("Someone <s@example.org>");
+ model->appendBatch({ read, unread });
+
+ window.resize(900, 300);
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ view->selectAll();
+ QApplication::processEvents();
+
+ const int rowHeight = view->rowHeight(0);
+ QVERIFY(rowHeight > 0);
+
+ QImage shot(view->viewport()->size(), QImage::Format_ARGB32);
+ shot.fill(Qt::transparent);
+ view->viewport()->render(&shot);
+
+ int differing = 0;
+ for (int y = 0; y < rowHeight && y + rowHeight < shot.height(); ++y)
+ for (int x = 0; x < shot.width(); ++x)
+ if (shot.pixel(x, y) != shot.pixel(x, y + rowHeight))
+ ++differing;
+
+ QVERIFY2(differing == 0,
+ qPrintable(QStringLiteral("a selected read row paints differently "
+ "from a selected unread one (%1 pixels): "
+ "the dimming is overriding the selection "
+ "highlight").arg(differing)));
+}
+
void TestMainWindow::markAllReadIsDisabledUntilTheQueryFinishes()
{
// Threads arrive in batches, so acting mid-load would silently skip
diff --git a/tests/test_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp
index a9cdb73..b880c29 100644
--- a/tests/test_threadlistmodel.cpp
+++ b/tests/test_threadlistmodel.cpp
@@ -33,6 +33,9 @@ private slots:
void reportsSubjectAndAuthors();
void subjectShowsMessageCountOnlyForRealThreads();
void unreadThreadsRenderBold();
+ void readThreadsAreDimmedAndUnreadAreNot();
+ void theUnreadCueDoesNotDependOnFontWeight();
+ void aDoomedThreadKeepsItsContrastEvenWhenRead();
void tagsAreTheFirstColumnAndSubjectTheLast();
void accountTagBecomesAChipLabel();
void unreadStylingSurvivesAnAccountChip();
@@ -164,6 +167,79 @@ void TestThreadListModel::unreadThreadsRenderBold()
QVERIFY(unreadFont.value<QFont>().bold());
}
+void TestThreadListModel::readThreadsAreDimmedAndUnreadAreNot()
+{
+ // Bold was unread's ONLY cue, and on the user's system it renders
+ // identically to regular: verified with a bare QTableView and a plain
+ // QStandardItemModel, so the fault is below this application, in Qt or
+ // fontconfig, and no model change can reach it. Bold is kept, since it
+ // works elsewhere, but the state can no longer depend on it.
+ //
+ // Read rows are dimmed instead, which inverts the emphasis: unread sits at
+ // full contrast and the bulk of a mostly-read list recedes.
+ ThreadListModel model;
+ ThreadSummary read = makeThread(QStringLiteral("t1"), QStringLiteral("read"));
+ read.tags = QStringList{ QStringLiteral("inbox") };
+ model.appendBatch(
+ { read, makeThread(QStringLiteral("t2"), QStringLiteral("unread")) });
+
+ const QVariant readFg =
+ model.data(model.index(0, ThreadListModel::SubjectColumn),
+ Qt::ForegroundRole);
+ const QVariant unreadFg =
+ model.data(model.index(1, ThreadListModel::SubjectColumn),
+ Qt::ForegroundRole);
+
+ QVERIFY2(readFg.isValid(), "a read thread carries no dimming");
+ QVERIFY2(!unreadFg.isValid(),
+ "an unread thread must be left at the palette's own colour, so it "
+ "is the one that stands out");
+}
+
+void TestThreadListModel::theUnreadCueDoesNotDependOnFontWeight()
+{
+ // The property that matters, stated directly: strip every font from the
+ // model's answer and the two states must still be distinguishable. A test
+ // asserting only that bold is set passes on a system where bold paints
+ // exactly like regular, which is precisely how this went unnoticed.
+ ThreadListModel model;
+ ThreadSummary read = makeThread(QStringLiteral("t1"), QStringLiteral("read"));
+ read.tags = QStringList{ QStringLiteral("inbox") };
+ model.appendBatch(
+ { read, makeThread(QStringLiteral("t2"), QStringLiteral("unread")) });
+
+ for (int column = 0; column < ThreadListModel::ColumnCount; ++column) {
+ const QVariant readFg =
+ model.data(model.index(0, column), Qt::ForegroundRole);
+ const QVariant unreadFg =
+ model.data(model.index(1, column), Qt::ForegroundRole);
+
+ QVERIFY2(readFg != unreadFg,
+ qPrintable(QStringLiteral("column %1 renders read and unread "
+ "identically once the font is "
+ "ignored").arg(column)));
+ }
+}
+
+void TestThreadListModel::aDoomedThreadKeepsItsContrastEvenWhenRead()
+{
+ // Both cues write ForegroundRole, so they share one channel and the order
+ // matters. A deleted row forces white text onto its crimson fill; dimming
+ // it because it also happens to be read would drop that contrast to
+ // unreadable.
+ ThreadListModel model;
+ ThreadSummary thread = makeThread(QStringLiteral("t1"),
+ QStringLiteral("doomed and read"));
+ thread.tags = QStringList{ QStringLiteral("inbox") };
+ model.appendBatch({ thread });
+
+ model.applyTagChange(QStringLiteral("t1"), { QStringLiteral("deleted") }, {});
+
+ const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn);
+ QCOMPARE(model.data(subject, Qt::ForegroundRole).value<QBrush>().color(),
+ QColor(Qt::white));
+}
+
void TestThreadListModel::tagsAreTheFirstColumnAndSubjectTheLast()
{
// Subject stretches to fill the view, so whatever sits after it is pushed
@@ -325,9 +401,17 @@ void TestThreadListModel::ordinaryThreadsCarryNoRowColour()
const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn);
QVERIFY(!model.data(subject, Qt::BackgroundRole).isValid());
- QVERIFY(!model.data(subject, Qt::ForegroundRole).isValid());
const QVariant font = model.data(subject, Qt::FontRole);
QVERIFY(!font.isValid() || !font.value<QFont>().strikeOut());
+
+ // The foreground goes back to the dimming a read thread carries, NOT to
+ // nothing: this thread has no unread tag, so plain for it means dimmed.
+ // What matters is that the doomed white is gone.
+ const QVariant foreground = model.data(subject, Qt::ForegroundRole);
+ if (foreground.isValid()) {
+ QVERIFY2(foreground.value<QBrush>().color() != QColor(Qt::white),
+ "the doomed white text survived the undo");
+ }
}
void TestThreadListModel::threadIdIsReachableFromAnIndex()