diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_mainwindow.cpp | 55 | ||||
| -rw-r--r-- | tests/test_threadlistmodel.cpp | 86 |
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() |
