diff options
Diffstat (limited to 'tests/test_mainwindow.cpp')
| -rw-r--r-- | tests/test_mainwindow.cpp | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index e40fd3f..4a911c9 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -34,7 +34,9 @@ #include <QStandardPaths> #include <QTemporaryDir> +#include <QStyle> #include <QTableView> +#include <QToolBar> #include <QTimer> #include "config.h" @@ -113,6 +115,11 @@ private slots: void anUnknownExternalStateClearsNothing(); void aSuccessfulCronSyncDrainsTheEditedAccounts(); void aCronSyncDoesNotClearAnEditMadeWhileItRan(); + + void everyActionCarriesAnIcon(); + void theToolbarDoesNotOverrideTheDesktopButtonStyle(); + void theImportantActionIsLabelledImportant(); + void theImportantActionStillWritesTheFlaggedTag(); }; void TestMainWindow::everyKnownActionIsRegistered() @@ -2378,6 +2385,134 @@ void TestMainWindow::aCronSyncDoesNotClearAnEditMadeWhileItRan() "an edit made after the sync ended was swallowed by it"); } +// Items 56 and 57. + +void TestMainWindow::everyActionCarriesAnIcon() +{ + // Item 56. The complaint was inconsistency, not absence: eight actions had + // themed icons and the other sixteen had none, so adjacent entries in one + // menu disagreed, and the toolbar's TextBesideIcon style laid out an empty + // slot for each of the sixteen. + // + // What this test can and cannot prove is worth stating, because it is + // weaker than it looks. QIcon::fromTheme() resolves against the icon theme + // of the machine running the test, so a PASS says "this desktop has art for + // every name assigned", not "every name is correct" and not "the icon suits + // the action". A machine with a sparse theme fails this through no fault of + // the code. It is still worth having: it catches the actual regression, + // which is an action registered with no name assigned at all. + const Config config; + MainWindow window(config); + + // The guard. Without it a MainWindow that registered nothing would pass an + // empty loop, which is the classic way a "for each" assertion goes vacuous. + const QList<QAction *> actions = + window.findChildren<QAction *>(QString(), Qt::FindDirectChildrenOnly); + QVERIFY2(actions.size() >= KeyMap::knownActions().size(), + qPrintable(QStringLiteral("expected at least %1 actions, found %2") + .arg(KeyMap::knownActions().size()) + .arg(actions.size()))); + + QStringList missing; + for (const QString &name : KeyMap::knownActions()) { + auto *action = window.findChild<QAction *>(name); + QVERIFY2(action, qPrintable(QStringLiteral("no action named %1").arg(name))); + if (action->icon().isNull()) + missing.append(name); + } + + QVERIFY2(missing.isEmpty(), + qPrintable(QStringLiteral("%1 action(s) have no icon: %2") + .arg(missing.size()) + .arg(missing.join(QStringLiteral(", "))))); +} + +void TestMainWindow::theToolbarDoesNotOverrideTheDesktopButtonStyle() +{ + // The second half of the user's note: "Buttons should honor the 'Icon only' + // option". They cannot while the toolbar asserts its own style. Qt takes + // the desktop's preference from the platform theme and exposes it as + // SH_ToolButtonStyle; a hardcoded setToolButtonStyle() overrides it, so the + // user's setting has no effect whatever it is set to. + const Config config; + MainWindow window(config); + + auto *toolBar = window.findChild<QToolBar *>(QStringLiteral("main_toolbar")); + QVERIFY(toolBar); + + const auto expected = static_cast<Qt::ToolButtonStyle>( + window.style()->styleHint(QStyle::SH_ToolButtonStyle, nullptr, toolBar)); + + QCOMPARE(toolBar->toolButtonStyle(), expected); +} + +void TestMainWindow::theImportantActionIsLabelledImportant() +{ + // Item 57. The user picked "Important" over "Starred": the Message menu + // already has `Mark &spam`, so "Starred" would have had to take an + // accelerator from inside the word, while "Important" takes a free &I. + const Config config; + MainWindow window(config); + + // The action NAME is unchanged on purpose. It is the key a user writes in + // the config's [keys] section, so renaming it would silently break every + // existing binding for a change that is only about wording. + auto *action = window.findChild<QAction *>(QStringLiteral("flag")); + QVERIFY(action); + + QVERIFY2(action->text().contains(QStringLiteral("Important")), + qPrintable(QStringLiteral("the action still reads '%1'") + .arg(action->text()))); + QVERIFY2(!action->text().contains(QStringLiteral("Flag")), + qPrintable(QStringLiteral("the action still reads '%1'") + .arg(action->text()))); + + // The accelerator the item chose, and the reason "Starred" was rejected. + QCOMPARE(action->text(), QStringLiteral("&Important")); +} + +void TestMainWindow::theImportantActionStillWritesTheFlaggedTag() +{ + // The rename is a LABEL change and must not reach the mail store. `flagged` + // is a notmuch tag: neomutt reads it, the user's saved queries match on it, + // ThreadSummary::isFlagged() tests for it and TagColors colours it. A + // rename that followed the label through to the tag would rewrite the store + // and desynchronise every other tool that reads the same Maildir. + // + // Asserted on the ids and tags actually sent to the worker, which is the + // only place the distinction is observable. + const Config config; + MainWindow window(config); + + auto *model = window.findChild<ThreadListModel *>(); + QVERIFY(model); + auto *view = window.findChild<QTableView *>(); + QVERIFY(view); + + model->appendBatch({ makeThread(QStringLiteral("t1"), + { QStringLiteral("inbox") }) }); + + // The guard: the thread must NOT already carry the tag, or the assertion + // below would pass against an action that did nothing at all. + QVERIFY(!model->threadAt(0).isFlagged()); + + view->selectRow(0); + + auto *action = window.findChild<QAction *>(QStringLiteral("flag")); + QVERIFY(action); + action->trigger(); + + // sendThreadTagChange() applies the change to the model optimistically, so + // the tag the action really wrote is observable here without a worker. + QVERIFY2(model->threadAt(0).isFlagged(), + "the renamed action no longer writes the `flagged` tag"); + QVERIFY2(model->threadAt(0).tags.contains(QStringLiteral("flagged")), + "the tag written was not `flagged`"); + QVERIFY2(!model->threadAt(0).tags.contains(QStringLiteral("important")), + "the rename reached the mail store: an `important` tag was " + "written, which no other tool reading this Maildir knows"); +} + // Constructing a MainWindow needs a QApplication and a platform plugin. The // test has no display under ctest, so it runs offscreen unless the caller // asked for something else. |
