diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-03 15:42:40 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-03 15:42:40 +0200 |
| commit | eb35acda4207392c10b4b1192b3b057bcad99a47 (patch) | |
| tree | c962e91fc502ec57d92df386e773033fe42fd17b /tests/test_threadlistmodel.cpp | |
| parent | afd20c9527fa77ba60901707c7bc73b2af926a67 (diff) | |
| parent | f62ced3c2c85675e746bff7ef8aca5c75c9737e0 (diff) | |
| download | qtmaildir-eb35acda4207392c10b4b1192b3b057bcad99a47.tar.gz qtmaildir-eb35acda4207392c10b4b1192b3b057bcad99a47.zip | |
Merge branch 'feature/qaction-menus'
Menus, a toolbar and a generated shortcut reference, built on converting
the action registry from a hash of callbacks to QActions. Along the way:
three default key bindings that had never fired, a shortcut dialog taller
than the screen, thread list columns that could not be resized, no visible
feedback that a tag action had landed, and a tags column so wide it was
unreadable.
Backlog items 3, 8, 9, 13 and 14 done; 11 partly.
Diffstat (limited to 'tests/test_threadlistmodel.cpp')
| -rw-r--r-- | tests/test_threadlistmodel.cpp | 182 |
1 files changed, 179 insertions, 3 deletions
diff --git a/tests/test_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp index 24ba9e2..e8a5fa8 100644 --- a/tests/test_threadlistmodel.cpp +++ b/tests/test_threadlistmodel.cpp @@ -33,6 +33,14 @@ private slots: void reportsSubjectAndAuthors(); void subjectShowsMessageCountOnlyForRealThreads(); void unreadThreadsRenderBold(); + void tagsAreTheFirstColumnAndSubjectTheLast(); + void accountTagBecomesAChipLabel(); + void unreadStylingSurvivesAnAccountChip(); + void accountChipUsesTheConfiguredColour(); + void deletedThreadsAreRedAndStruckThrough(); + void spamThreadsAreOrangeAndStruckThrough(); + void doomedStylingCoversEveryColumn(); + void ordinaryThreadsCarryNoRowColour(); void threadIdIsReachableFromAnIndex(); void invalidIndexesReturnNothing(); void threadAtOutOfRangeIsSafe(); @@ -113,9 +121,11 @@ void TestThreadListModel::reportsSubjectAndAuthors() const QModelIndex date = model.index(0, ThreadListModel::DateColumn); QVERIFY(!model.data(date, Qt::DisplayRole).toString().isEmpty()); - const QModelIndex tags = model.index(0, ThreadListModel::TagsColumn); - QCOMPARE(model.data(tags, Qt::DisplayRole).toString(), - QStringLiteral("inbox unread")); + // Tags are no longer a column; they reach the strip under the message + // pane through a role instead. + const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn); + QCOMPARE(model.data(subject, ThreadListModel::TagsRole).toStringList(), + QStringList({ QStringLiteral("inbox"), QStringLiteral("unread") })); } void TestThreadListModel::subjectShowsMessageCountOnlyForRealThreads() @@ -153,6 +163,172 @@ void TestThreadListModel::unreadThreadsRenderBold() QVERIFY(unreadFont.value<QFont>().bold()); } +void TestThreadListModel::tagsAreTheFirstColumnAndSubjectTheLast() +{ + // Subject stretches to fill the view, so whatever sits after it is pushed + // off-screen. Tags used to be there, which is why acting on a thread + // looked like it did nothing: the only column that changed was invisible. + QCOMPARE(ThreadListModel::SubjectColumn, ThreadListModel::ColumnCount - 1); + + ThreadListModel model; + model.appendBatch({ makeThread(QStringLiteral("t1"), QStringLiteral("hello")) }); + QCOMPARE(model.headerData(ThreadListModel::SubjectColumn, Qt::Horizontal, + Qt::DisplayRole).toString(), + QStringLiteral("Subject")); + + // No tags column at all: spelling out a dozen tags per row consumed most + // of the list's width and was unreadable. + for (int column = 0; column < ThreadListModel::ColumnCount; ++column) { + QVERIFY(model.headerData(column, Qt::Horizontal, Qt::DisplayRole) + .toString() != QStringLiteral("Tags")); + } +} + +void TestThreadListModel::accountTagBecomesAChipLabel() +{ + // The account tag is a different taxonomy from a functional one: which + // mailbox the thread arrived in. It renders as a chip in front of the + // subject, so the model exposes its label and colour separately. + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("hello")); + thread.tags = QStringList{ QStringLiteral("inbox"), + QStringLiteral("account-gmail-danixland") }; + model.appendBatch({ thread }); + + const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn); + QCOMPARE(model.data(subject, ThreadListModel::AccountLabelRole).toString(), + QStringLiteral("gmail-danixland")); + QVERIFY(model.data(subject, ThreadListModel::AccountColourRole) + .value<QColor>().isValid()); + + // A thread with no account tag gets no chip rather than an empty one. + ThreadListModel plain; + ThreadSummary untagged = makeThread(QStringLiteral("t2"), QStringLiteral("hi")); + untagged.tags = QStringList{ QStringLiteral("inbox") }; + plain.appendBatch({ untagged }); + QVERIFY(plain.data(plain.index(0, ThreadListModel::SubjectColumn), + ThreadListModel::AccountLabelRole).toString().isEmpty()); +} + +void TestThreadListModel::unreadStylingSurvivesAnAccountChip() +{ + // The subject cell is drawn by a delegate when the thread has an account + // chip. The delegate paints the text itself, so it has to keep honouring + // the model's font: otherwise an unread thread stops rendering bold for + // exactly those threads that carry an account tag, which is all of them. + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("hello")); + thread.tags = QStringList{ QStringLiteral("inbox"), QStringLiteral("unread"), + QStringLiteral("account-gmail-danixland") }; + model.appendBatch({ thread }); + + const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn); + QVERIFY(!model.data(subject, ThreadListModel::AccountLabelRole) + .toString().isEmpty()); + + const QVariant font = model.data(subject, Qt::FontRole); + QVERIFY2(font.isValid(), "unread thread with an account tag has no font"); + QVERIFY2(font.value<QFont>().bold(), "unread thread is not bold"); +} + +void TestThreadListModel::accountChipUsesTheConfiguredColour() +{ + // The colour comes from the account's own stanza, so a configured one must + // reach the chip rather than the generated fallback. + TagColors colours; + colours.setAccountColour(QStringLiteral("gmail-danixland"), + QColor(QStringLiteral("#cc0000"))); + + ThreadListModel model; + model.setTagColors(&colours); + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("hello")); + thread.tags = QStringList{ QStringLiteral("account-gmail-danixland") }; + model.appendBatch({ thread }); + + QCOMPARE(model.data(model.index(0, ThreadListModel::SubjectColumn), + ThreadListModel::AccountColourRole).value<QColor>(), + QColor(QStringLiteral("#cc0000"))); +} + +void TestThreadListModel::deletedThreadsAreRedAndStruckThrough() +{ + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("doomed")); + thread.tags = QStringList{ QStringLiteral("inbox") }; + model.appendBatch({ thread }); + + const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn); + QVERIFY(!model.data(subject, Qt::BackgroundRole).isValid()); + + model.applyTagChange(QStringLiteral("t1"), { QStringLiteral("deleted") }, {}); + + const QVariant background = model.data(subject, Qt::BackgroundRole); + QVERIFY(background.isValid()); + QCOMPARE(background.value<QBrush>().color(), ThreadListModel::deletedColour()); + + // White text on the fill, and struck through so the state reads even in a + // screenshot with the colours stripped. + QCOMPARE(model.data(subject, Qt::ForegroundRole).value<QBrush>().color(), + QColor(Qt::white)); + QVERIFY(model.data(subject, Qt::FontRole).value<QFont>().strikeOut()); +} + +void TestThreadListModel::spamThreadsAreOrangeAndStruckThrough() +{ + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("junk")); + thread.tags = QStringList{ QStringLiteral("inbox") }; + model.appendBatch({ thread }); + + model.applyTagChange(QStringLiteral("t1"), { QStringLiteral("spam") }, {}); + + const QModelIndex subject = model.index(0, ThreadListModel::SubjectColumn); + QCOMPARE(model.data(subject, Qt::BackgroundRole).value<QBrush>().color(), + ThreadListModel::spamColour()); + QVERIFY(model.data(subject, Qt::FontRole).value<QFont>().strikeOut()); + + // Spam and deleted must be distinguishable, not two shades of one colour. + QVERIFY(ThreadListModel::spamColour() != ThreadListModel::deletedColour()); +} + +void TestThreadListModel::doomedStylingCoversEveryColumn() +{ + // A cue on one column would vanish the moment that column scrolled out of + // view, which is the bug this whole change exists to fix. + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("doomed")); + thread.tags = QStringList{ QStringLiteral("inbox") }; + model.appendBatch({ thread }); + + model.applyTagChange(QStringLiteral("t1"), { QStringLiteral("deleted") }, {}); + + for (int column = 0; column < ThreadListModel::ColumnCount; ++column) { + const QModelIndex index = model.index(0, column); + QVERIFY2(model.data(index, Qt::BackgroundRole).isValid(), + qPrintable(QStringLiteral("column %1 has no background").arg(column))); + QVERIFY2(model.data(index, Qt::FontRole).value<QFont>().strikeOut(), + qPrintable(QStringLiteral("column %1 is not struck through").arg(column))); + } +} + +void TestThreadListModel::ordinaryThreadsCarryNoRowColour() +{ + // Undo has to restore the plain look, not merely drop the tag. + ThreadListModel model; + ThreadSummary thread = makeThread(QStringLiteral("t1"), QStringLiteral("normal")); + thread.tags = QStringList{ QStringLiteral("inbox") }; + model.appendBatch({ thread }); + + model.applyTagChange(QStringLiteral("t1"), { QStringLiteral("deleted") }, {}); + model.applyTagChange(QStringLiteral("t1"), {}, { QStringLiteral("deleted") }); + + 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()); +} + void TestThreadListModel::threadIdIsReachableFromAnIndex() { // The view hands MainWindow a QModelIndex; the worker needs a thread id. |
