aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_messageview.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-17 13:49:08 +0200
committerDanilo M. <danix@danix.xyz>2026-08-17 13:49:08 +0200
commit435a41c8476a4e94f28ad7730351498465194454 (patch)
tree0acd8c175caa24a021c57f701394680e21c4e3dd /tests/test_messageview.cpp
parentabf56abd167109f50e0ddb06e1baf92192174cd7 (diff)
downloadqtmaildir-435a41c8476a4e94f28ad7730351498465194454.tar.gz
qtmaildir-435a41c8476a4e94f28ad7730351498465194454.zip
fix(ui): drop the browser's own actions from the message pane menu
The pane's context menu started from QWebEngineView::createStandardContextMenu() and kept it whole, so it offered Back, Forward, Reload and Save page. None of them can apply: every message is rendered with setHtml() from memory, so there is no history to go back to and nothing to reload, and the request interceptor blocks everything by default. They were inert as well as meaningless. removeBrowserActions() matches on the QAction pointer returned by page->action(), never on the entry's text, which is translated: a text match would work in English and fail in every other locale, which is a defect no test written in English would catch. Removing entries also strands separators at the edges or doubles them up, which reads as a menu that lost something, so the filter sweeps them; Qt offers nothing for this. View source is deliberately NOT filtered. It was removed with the other four at first, which was an overreach: the user asked for four and view-source has a real document and a real use. Chromium's own entry cannot work here either, since it navigates to view-source:<url> and MessagePage refuses that, so backlog item 113 implements it as our own plain-text dialog. The test builds a menu by hand, which is right for testing the filter and proves nothing about what Chromium's real menu contains. That limit is stated at the test, and is why it does not assert on SelectAll: the real menu has never offered it, verified by hand against a build with this filter reverted (backlog item 117). Backlog item 100.
Diffstat (limited to 'tests/test_messageview.cpp')
-rw-r--r--tests/test_messageview.cpp80
1 files changed, 80 insertions, 0 deletions
diff --git a/tests/test_messageview.cpp b/tests/test_messageview.cpp
index c97a216..87234aa 100644
--- a/tests/test_messageview.cpp
+++ b/tests/test_messageview.cpp
@@ -17,8 +17,10 @@
*/
#include <QLabel>
+#include <QMenu>
#include <QPushButton>
#include <QSignalSpy>
+#include <QWebEnginePage>
#include <QWebEngineUrlScheme>
#include <QWebEngineView>
#include <QtTest>
@@ -54,6 +56,7 @@ private slots:
void headerOffersNoSenderForARealThread();
void headerOffersNothingForAnAbsentField();
void bodySelectionBecomesAQuotedSearch();
+ void theBodyMenuDropsTheBrowsersOwnActions();
void aSearchFromTheDetailsDialogClosesIt();
private:
@@ -699,6 +702,83 @@ void TestMessageView::bodySelectionBecomesAQuotedSearch()
.label.isEmpty());
}
+void TestMessageView::theBodyMenuDropsTheBrowsersOwnActions()
+{
+ // Item 100. The pane starts from Chromium's standard context menu, which
+ // is built for a browser: Back, Forward, Reload, Save page and View source
+ // all arrive with it and none of them can apply, since every document
+ // comes through setHtml() with a fixed base URL and the interceptor blocks
+ // everything by default.
+ //
+ // Asserted on the ACTION POINTERS, which is also how the production code
+ // matches them. Matching on text would pass here and fail in every locale
+ // but English, and an untranslated match is exactly the defect this could
+ // reintroduce without any test noticing.
+ MessageView view;
+ auto *page = view.findChild<QWebEnginePage *>();
+ QVERIFY2(page, "no page, so this test would assert nothing");
+
+ QMenu menu;
+ const QList<QWebEnginePage::WebAction> unwanted = {
+ QWebEnginePage::Back, QWebEnginePage::Forward,
+ QWebEnginePage::Reload, QWebEnginePage::SavePage,
+ };
+ // Kept, and the reason the standard menu is used at all rather than being
+ // rebuilt from scratch.
+ //
+ // ViewSource is in this list deliberately. It was removed with the four
+ // above at first, which was an overreach: it has a real document and a
+ // real use, and item 113 implements it properly. A test asserting it is
+ // GONE would lock in the overreach, so it asserts it survives.
+ //
+ // SelectAll is deliberately NOT here, and the reason is a limit of this
+ // test worth stating. This menu is built BY HAND, so "SelectAll survives"
+ // would only prove the filter does not remove it, and prove nothing about
+ // whether Chromium's real menu ever offers it. Measured by hand on
+ // 2026-08-17, with a selection active: the real menu holds Copy and the
+ // search entries and no Select all, both before and after this filter
+ // existed. Asserting on it here would read as a guarantee the code does
+ // not make. See item 117.
+ const QList<QWebEnginePage::WebAction> wanted = {
+ QWebEnginePage::Copy,
+ QWebEnginePage::ViewSource,
+ };
+
+ for (const QWebEnginePage::WebAction which : unwanted)
+ menu.addAction(page->action(which));
+ menu.addSeparator();
+ for (const QWebEnginePage::WebAction which : wanted)
+ menu.addAction(page->action(which));
+
+ // The guard: the menu really does hold what the assertions below are about,
+ // so a filter that removed everything, or a page that offered nothing,
+ // cannot pass by accident.
+ QCOMPARE(menu.actions().size(), unwanted.size() + wanted.size() + 1);
+
+ MessageView::removeBrowserActions(&menu, page);
+
+ const QList<QAction *> left = menu.actions();
+ for (const QWebEnginePage::WebAction which : unwanted) {
+ QVERIFY2(!left.contains(page->action(which)),
+ qPrintable(QStringLiteral(
+ "a browser action survived the filter: %1")
+ .arg(page->action(which)->text())));
+ }
+ for (const QWebEnginePage::WebAction which : wanted) {
+ QVERIFY2(left.contains(page->action(which)),
+ qPrintable(QStringLiteral(
+ "the filter removed an action the pane needs: "
+ "%1")
+ .arg(page->action(which)->text())));
+ }
+
+ // No separator left stranded at either edge by the removals, which reads
+ // as a menu that lost something.
+ QVERIFY(!left.isEmpty());
+ QVERIFY(!left.constFirst()->isSeparator());
+ QVERIFY(!left.constLast()->isSeparator());
+}
+
void TestMessageView::aSearchFromTheDetailsDialogClosesIt()
{
// The dialog is modal. Without closing it, the query runs and the thread