summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/mainwindow.cpp25
-rw-r--r--tests/test_mainwindow.cpp69
2 files changed, 90 insertions, 4 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6bd91d9..bf1a33b 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1779,12 +1779,29 @@ void MainWindow::buildSavedQueryRow(QWidget *parent, QVBoxLayout *layout)
auto *menu = new QMenu(menuButton);
for (const SavedQuery &saved : unpinned) {
QAction *action = menu->addAction(saved.name);
- connect(action, &QAction::triggered, this,
- [this, saved]() { runSavedQuery(saved); });
+
// A menu entry has no context menu of its own, so its own submenu
- // carries the same three actions; an unpinned query would
- // otherwise be the one thing that cannot be edited or deleted.
+ // carries the same actions; an unpinned query would otherwise be
+ // the one thing that cannot be edited or deleted.
auto *entryMenu = new QMenu(menu);
+
+ // Running the query is an item INSIDE that submenu, and must be:
+ // Qt does not emit triggered for an action that owns a menu, so a
+ // connection on `action` itself never fires and clicking the entry
+ // only opens the submenu. That shipped, and went unnoticed while
+ // the menu was the rarely-used half and the user's queries were
+ // pinned buttons. Item 93 moved every query into the menu, and item
+ // 94 makes it their only home.
+ auto *run = new QAction(tr("Run"), entryMenu);
+ run->setObjectName(QStringLiteral("runQuery"));
+ connect(run, &QAction::triggered, this,
+ [this, saved]() { runSavedQuery(saved); });
+ entryMenu->addAction(run);
+
+ auto *runSeparator = new QAction(entryMenu);
+ runSeparator->setSeparator(true);
+ entryMenu->addAction(runSeparator);
+
addSavedQueryActions(entryMenu, saved);
action->setMenu(entryMenu);
}
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 4cc9954..6a049e3 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -169,6 +169,7 @@ private slots:
void narrowingAnEmptyQueryBarIsAPlainSearch();
void aMalformedAccountIsReportedWithoutBlockingTheConstructor();
void aWorkerBackedWindowReturnsRealThreads();
+ void aQueryInTheMenuCanActuallyBeRun();
void theFourBuiltinFiltersAreOnTheRowInOrder();
void aFilterComposesWithTheSelectedAccount();
void aFilterAcrossAllAccountsIsUnscoped();
@@ -6349,6 +6350,74 @@ void TestMainWindow::aWorkerBackedWindowReturnsRealThreads()
QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(QModelIndex()) == 1, 15000);
}
+void TestMainWindow::aQueryInTheMenuCanActuallyBeRun()
+{
+ // An unpinned query was UNRUNNABLE. Its action carried both a triggered
+ // connection and a submenu of edit actions, and Qt does not emit triggered
+ // for an action that owns a menu: clicking it opens the submenu and nothing
+ // else. The connection had never fired.
+ //
+ // It shipped unnoticed because the menu was the rarely-used half while the
+ // user's queries were pinned buttons. Item 93 moved every one of them into
+ // the menu, which is how it surfaced, and item 94 makes the menu their only
+ // home, so this is the path that has to work.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ Config config;
+ loadWithQueries(config, dir, QStringLiteral(R"({
+ "version": 1,
+ "queries": [
+ { "name": "Menued", "query": "tag:menued", "pinned": false }
+ ]
+ })"));
+
+ MainWindow window(config);
+ auto *queryEdit = window.findChild<QLineEdit *>(QStringLiteral("queryEdit"));
+ QVERIFY(queryEdit);
+
+ auto *menuButton =
+ window.findChild<QPushButton *>(QStringLiteral("savedQueryMenuButton"));
+ QVERIFY2(menuButton, "no overflow menu for an unpinned query");
+ QVERIFY(menuButton->menu());
+
+ QAction *entry = nullptr;
+ for (QAction *action : menuButton->menu()->actions()) {
+ if (action->text() == QStringLiteral("Menued"))
+ entry = action;
+ }
+ QVERIFY2(entry, "the unpinned query is not in the menu");
+
+ // The entry keeps its submenu, because an unpinned query must still be
+ // editable and deletable. What it cannot be is the ONLY thing there: Qt
+ // does not emit triggered for an action that owns a menu, so running the
+ // query needs an item of its own.
+ QVERIFY2(entry->menu(), "the per-query actions are gone");
+
+ QAction *run = nullptr;
+ for (QAction *action : entry->menu()->actions()) {
+ if (action->objectName() == QStringLiteral("runQuery"))
+ run = action;
+ }
+ QVERIFY2(run, "no way to run the query: its submenu offers only edit "
+ "actions, and Qt never emits triggered for the parent");
+
+ // First, before the edit actions. Running is what the entry is for; editing
+ // is what one does to it occasionally.
+ QCOMPARE(entry->menu()->actions().constFirst(), run);
+
+ run->trigger();
+ QCOMPARE(queryEdit->text(), QStringLiteral("tag:menued"));
+
+ // The edit actions survived beside it.
+ QStringList names;
+ for (QAction *action : entry->menu()->actions())
+ names.append(action->objectName());
+ QVERIFY2(names.contains(QStringLiteral("editQuery")),
+ "the entry lost Edit");
+ QVERIFY2(names.contains(QStringLiteral("deleteQuery")),
+ "the entry lost Delete");
+}
+
void TestMainWindow::theFourBuiltinFiltersAreOnTheRowInOrder()
{
// Shipped, not pinned. Nothing in this config names a query, so a row with