From 8913a1190c48ff2672f8783af8594ca11e49ccf7 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 13 Aug 2026 11:43:16 +0200 Subject: feat(rules): report the text-mode refusal without a modal Leaving text mode with a query the builder cannot represent has to refuse, since there are no rows that mean that query. It announced this with a QMessageBox, which made the branch untestable: a modal blocks the test that reaches it, so the one path that can strand a user was the one path shipping unverified. Say it in the warning label the dialog already has instead. That also suits the moment better, since it does not interrupt someone mid-edit to tell them something the label can hold while they keep typing, and it matches how the tag dialog reports a bad tag. Returning to the rows now calls showWarnings(), because the refusal writes into the same label the load warnings use and a stale complaint would otherwise outlive the query that caused it. The test drives the refusal and the recovery, and asserts the warning appears and then clears. Verified by mutation: letting the checkbox clear regardless fails it. warningTextForTest uses isVisibleTo rather than isVisible. Every child of a dialog that was never shown reports isVisible() false, so the seam would have reported no warning whatever the label held, which is a probe that cannot see the thing it checks. --- tests/test_tagrules.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'tests/test_tagrules.cpp') diff --git a/tests/test_tagrules.cpp b/tests/test_tagrules.cpp index 49abf8a..2302111 100644 --- a/tests/test_tagrules.cpp +++ b/tests/test_tagrules.cpp @@ -46,6 +46,7 @@ private slots: void anUnrepresentableRuleOpensInTextMode(); void editingARowRewritesTheQuery(); void aTextModeRuleStaysTextWhenAnotherRuleIsVisited(); + void leavingTextModeIsRefusedWhenTheQueryCannotBeShownAsRows(); private: QString writeRules(const QString &json); @@ -494,5 +495,54 @@ void TestTagRules::aTextModeRuleStaysTextWhenAnotherRuleIsVisited() QStringLiteral("from:plain.example.org")); } +void TestTagRules::leavingTextModeIsRefusedWhenTheQueryCannotBeShownAsRows() +{ + // The refusal is the only path that can strand a user, so it is the one + // most worth pinning. It reports through the warning label rather than a + // modal, which is what lets this test exist at all: a modal would block + // here and the branch would ship unverified. + QTemporaryDir configHome; + QVERIFY(configHome.isValid()); + qputenv("XDG_CONFIG_HOME", configHome.path().toUtf8()); + QVERIFY(QDir().mkpath(configHome.filePath(QStringLiteral("mailrules")))); + + const QString stored = configHome.filePath( + QStringLiteral("mailrules/rules.json")); + QFile out(stored); + QVERIFY(out.open(QIODevice::WriteOnly)); + out.write(R"({ + "version": 1, + "rules": [ + {"id": "vendor", "query": "from:vendor.example.org", + "add": ["vendor"], "stage": 50, "enabled": true} + ] + })"); + out.close(); + + TagRulesDialog dialog; + QVERIFY(!dialog.textModeForTest()); + + dialog.setTextModeForTest(true); + QVERIFY(dialog.textModeForTest()); + + // Type something notmuch accepts and this builder does not model. + dialog.setQueryTextForTest(QStringLiteral("body:receipt")); + dialog.setTextModeForTest(false); + + QVERIFY2(dialog.textModeForTest(), + "the checkbox must refuse to clear: no rows mean this query"); + QVERIFY2(!dialog.warningTextForTest().isEmpty(), + "the refusal must say why, not fail silently"); + + // And a representable query lets the builder back, clearing the warning. + dialog.setQueryTextForTest(QStringLiteral("from:other.example.org")); + dialog.setTextModeForTest(false); + + QVERIFY2(!dialog.textModeForTest(), "a representable query must return"); + QCOMPARE(dialog.rowCountForTest(), 1); + QVERIFY2(dialog.warningTextForTest().isEmpty(), + "a stale refusal must not outlive the query that caused it"); +} + QTEST_MAIN(TestTagRules) #include "test_tagrules.moc" -- cgit v1.2.3