aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 14:17:09 +0200
committerDanilo M. <danix@danix.xyz>2026-08-03 14:17:09 +0200
commit1f2eddff6afcbf4c24f06e982e9169219429a2ed (patch)
treeb809a3221d32e0276a3156ac2a2a45ea7f73b825 /tests/test_mainwindow.cpp
parent188287f14f981ed3e8a08bc1514bb2ba6bb76809 (diff)
downloadqtmaildir-1f2eddff6afcbf4c24f06e982e9169219429a2ed.tar.gz
qtmaildir-1f2eddff6afcbf4c24f06e982e9169219429a2ed.zip
feat: add menus, a toolbar and a shortcut reference
Actions were a QHash of std::function dispatched by an event filter, which nothing could put in a menu. They are QActions now, bound from KeyMap so a [keys] override reaches the menus as well as the keyboard. Menu bar covers every action; the toolbar carries only Sync, Archive, Delete and Undo. Help > Keyboard shortcuts is generated from the actions, so it shows what the keys really do rather than a copy that drifts. spam and load_remote gained defaults, having been unreachable without a hand-written binding. The event filter is gone. Probing showed QAction shortcuts are dispatched before the focused widget sees the key, so they beat QAbstractItemView's type-to-search without one, and Qt already suppresses plain-letter shortcuts while an editable widget has focus. Dropping the filter's blanket guard also lets Ctrl+Q work while the query bar has focus. registeredActionNames() is derived from the actions rather than hand-maintained, so the two drift tests it needed are replaced by checks that a configured binding reaches its action. No confirmation dialogs: tag mutations still answer to undo.
Diffstat (limited to 'tests/test_mainwindow.cpp')
-rw-r--r--tests/test_mainwindow.cpp93
1 files changed, 87 insertions, 6 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 57eb763..6bfa925 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -18,18 +18,27 @@
#include <QtTest>
+#include <QAction>
+#include <QDir>
+#include <QSettings>
+#include <QTemporaryDir>
+
+#include "config.h"
#include "keymap.h"
#include "mainwindow.h"
-/// MainWindow is mostly wiring and needs a live QApplication plus a real
-/// database, so it is verified manually in Task 13. Two things do not need
-/// either, and both are the kind of drift a comment alone does not prevent.
+/// MainWindow is mostly wiring, and the parts that need a real database are
+/// still verified manually. What is checked here is the action registry: the
+/// bindings a user configures reach the QActions the menus and the keyboard
+/// both read from, and no action is left unreachable.
class TestMainWindow : public QObject
{
Q_OBJECT
private slots:
void everyKnownActionIsRegistered();
void everyRegisteredActionIsKnown();
+ void everyActionHasAShortcut();
+ void configuredBindingReachesTheAction();
void cidPrefixesAreBangFree();
void cidPrefixesAreDistinctPerMessage();
};
@@ -39,8 +48,14 @@ void TestMainWindow::everyKnownActionIsRegistered()
// KeyMap::knownActions() is what loadOverrides() validates config bindings
// against. An action listed there but never registered means a user can
// bind a key in qtmaildir.conf, get no warning, and have it do nothing.
+ //
+ // registeredActionNames() is now derived from the QActions themselves, so
+ // this compares against what the window really installed.
+ const Config config;
+ MainWindow window(config);
+
const QStringList known = KeyMap::knownActions();
- const QStringList registered = MainWindow::registeredActionNames();
+ const QStringList registered = window.registeredActionNames();
for (const QString &action : known) {
QVERIFY2(registered.contains(action),
@@ -53,8 +68,11 @@ void TestMainWindow::everyRegisteredActionIsKnown()
{
// The reverse drift: an action MainWindow implements but KeyMap rejects.
// The user would get "unknown action" for a binding that is really there.
+ const Config config;
+ MainWindow window(config);
+
const QStringList known = KeyMap::knownActions();
- const QStringList registered = MainWindow::registeredActionNames();
+ const QStringList registered = window.registeredActionNames();
for (const QString &action : registered) {
QVERIFY2(known.contains(action),
@@ -63,6 +81,57 @@ void TestMainWindow::everyRegisteredActionIsKnown()
}
}
+void TestMainWindow::everyActionHasAShortcut()
+{
+ // An action with no binding is unreachable from the keyboard. Every one
+ // of them carries a default, so an empty shortcut means the default table
+ // and the action list have drifted apart.
+ const Config config;
+ MainWindow window(config);
+
+ for (const QString &name : window.registeredActionNames()) {
+ const QAction *action = window.findChild<QAction *>(name);
+ QVERIFY2(action, qPrintable(QStringLiteral("no QAction named '%1'").arg(name)));
+ QVERIFY2(!action->shortcut().isEmpty(),
+ qPrintable(QStringLiteral("action '%1' has no shortcut").arg(name)));
+ }
+}
+
+void TestMainWindow::configuredBindingReachesTheAction()
+{
+ // The whole point of [keys]: a user's override must end up on the QAction,
+ // which is what both the keyboard and the menus read.
+ QTemporaryDir dir;
+ const QString path = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ {
+ QSettings s(path, QSettings::IniFormat);
+ s.beginGroup(QStringLiteral("keys"));
+ s.setValue(QStringLiteral("Ctrl+Alt+A"), QStringLiteral("archive"));
+ s.endGroup();
+ }
+
+ // MainWindow reads its keymap from Config::defaultPath(), so point that
+ // at the temporary file for this test.
+ const QString previous = qEnvironmentVariable("XDG_CONFIG_HOME");
+ QVERIFY(QDir().mkpath(dir.filePath(QStringLiteral("qtmaildir"))));
+ QVERIFY(QFile::copy(path, dir.filePath(QStringLiteral("qtmaildir/qtmaildir.conf"))));
+ qputenv("XDG_CONFIG_HOME", dir.path().toUtf8());
+
+ {
+ const Config config;
+ MainWindow window(config);
+ const QAction *archive =
+ window.findChild<QAction *>(QStringLiteral("archive"));
+ QVERIFY(archive);
+ QCOMPARE(archive->shortcut(), QKeySequence(QStringLiteral("Ctrl+Alt+A")));
+ }
+
+ if (previous.isEmpty())
+ qunsetenv("XDG_CONFIG_HOME");
+ else
+ qputenv("XDG_CONFIG_HOME", previous.toUtf8());
+}
+
void TestMainWindow::cidPrefixesAreBangFree()
{
// MainWindow is the only producer of cidPrefix in the application. The
@@ -89,5 +158,17 @@ void TestMainWindow::cidPrefixesAreDistinctPerMessage()
}
}
-QTEST_MAIN(TestMainWindow)
+// 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.
+int main(int argc, char *argv[])
+{
+ qputenv("QT_QPA_PLATFORM", qgetenv("QT_QPA_PLATFORM").isEmpty()
+ ? QByteArray("offscreen")
+ : qgetenv("QT_QPA_PLATFORM"));
+ QApplication app(argc, argv);
+ TestMainWindow test;
+ return QTest::qExec(&test, argc, argv);
+}
+
#include "test_mainwindow.moc"