From b1a7339120385f3fd8f1f5251ec20e3f0ff94b22 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 15 Aug 2026 11:09:29 +0200 Subject: fix(queries): make a query in the overflow menu runnable An unpinned query could not be run. Its menu 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 the entry only opened the submenu, so 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 query into the menu, which is how it surfaced, and item 94 makes the menu their only home, so this is now the path that has to work. Running is an item inside the submenu, first and above a separator, with the edit actions below it. The entry keeps its submenu because an unpinned query must still be editable and deletable. The test asserts the Run item exists and is first, then that triggering it reaches the query, then that Edit and Delete survived beside it. Restoring the old wiring fails it on the first of those, naming the Qt behaviour rather than just reporting a wrong query string. --- tests/test_mainwindow.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) (limited to 'tests') 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(QStringLiteral("queryEdit")); + QVERIFY(queryEdit); + + auto *menuButton = + window.findChild(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 -- cgit v1.2.3