diff options
| -rw-r--r-- | src/tagrulesdialog.cpp | 39 | ||||
| -rw-r--r-- | src/tagrulesdialog.h | 7 | ||||
| -rw-r--r-- | tests/test_tagrules.cpp | 50 |
3 files changed, 92 insertions, 4 deletions
diff --git a/src/tagrulesdialog.cpp b/src/tagrulesdialog.cpp index 1d31d07..86566ba 100644 --- a/src/tagrulesdialog.cpp +++ b/src/tagrulesdialog.cpp @@ -465,6 +465,26 @@ void TagRulesDialog::setRowValueForTest(int index, const QString &value) syncQueryLine(); } +void TagRulesDialog::setQueryTextForTest(const QString &text) +{ + m_query->setText(text); +} + +void TagRulesDialog::setTextModeForTest(bool on) +{ + m_textMode->setChecked(on); +} + +QString TagRulesDialog::warningTextForTest() const +{ + // isVisible() is false for every child of a dialog that was never shown, + // so it would report no warning whatever the label held. isVisibleTo() + // answers the question actually being asked: would this be on screen if + // the dialog were. + return m_warningLabel->isVisibleTo(this) ? m_warningLabel->text() + : QString(); +} + void TagRulesDialog::selectRuleForTest(int index) { if (index >= 0 && index < m_list->topLevelItemCount()) @@ -634,14 +654,19 @@ void TagRulesDialog::setTextMode(bool on) // Going back needs the typed query to be representable. If it is not, the // checkbox cannot clear: there are no rows that mean this query. + // + // Said in the warning label rather than a modal. A modal here would block + // any test that reaches this branch, which is how a refusal path ends up + // shipping unverified, and it interrupts someone who is mid-edit to tell + // them something the label can hold while they keep typing. const RuleQuery parsed = RuleQuery::parse(m_query->text().trimmed()); if (!parsed.parsed) { const QSignalBlocker block(m_textMode); m_textMode->setChecked(true); - QMessageBox::information( - this, tr("Cannot show as rows"), - tr("This query is more than the builder can show, so it stays " - "as text. It is still saved and applied normally.")); + m_warningLabel->setText( + tr("This query is more than the builder can show, so it stays as " + "text. It is still saved and applied normally.")); + m_warningLabel->setVisible(true); return; } @@ -653,6 +678,12 @@ void TagRulesDialog::setTextMode(bool on) m_loadedQuery = parsed; m_builder->setVisible(true); m_query->setReadOnly(true); + + // The refusal above writes into the same label the load warnings use, so + // a successful return to the rows must clear it or a stale complaint + // outlives the query that caused it. showWarnings() restores whatever the + // file itself had to say. + showWarnings(); } void TagRulesDialog::rebuildRows(const RuleQuery &query) diff --git a/src/tagrulesdialog.h b/src/tagrulesdialog.h index 9cee163..948ebe2 100644 --- a/src/tagrulesdialog.h +++ b/src/tagrulesdialog.h @@ -69,6 +69,13 @@ public: /// Runs the Save path without showing the dialog. void saveForTest() { onSave(); } + /// Types into the query field and toggles the mode, so the refusal path + /// can be reached without a synthetic click. The refusal reports through + /// the warning label rather than a modal precisely so this is testable. + void setQueryTextForTest(const QString &text); + void setTextModeForTest(bool on); + QString warningTextForTest() const; + signals: /// Asks the owner to run countQueries() through the worker. void countsRequested(); 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" |
