aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_config.cpp67
-rw-r--r--tests/test_mainwindow.cpp61
2 files changed, 126 insertions, 2 deletions
diff --git a/tests/test_config.cpp b/tests/test_config.cpp
index c46b153..4d56ec1 100644
--- a/tests/test_config.cpp
+++ b/tests/test_config.cpp
@@ -119,6 +119,9 @@ private slots:
void anAccountCarriesItsTrashFolder();
void aBracketedTrashFolderIsQuoted();
void anAccountWithoutATrashFolderWarns();
+ void theDraftsFilterComposesPerAccount();
+ void theDraftsFilterMatchesNothingWithoutAFolder();
+ void theDraftsFilterIsThreadedNotFlat();
void theTrashFilterComposesPerAccount();
void theTrashFilterMatchesNothingWithoutAFolder();
void anAccountWithoutASendCommandIsReceiveOnly();
@@ -1021,6 +1024,67 @@ void TestConfig::anAccountWithoutATrashFolderWarns()
QVERIFY(joined.contains(QStringLiteral("trash")));
}
+void TestConfig::theDraftsFilterComposesPerAccount()
+{
+ // Item 138. Follows `sent` and `trash`, which match a FOLDER: `draft` is a
+ // Maildir flag notmuch surfaces as a tag, but the folder is what the user
+ // means by Drafts, and a provider that flags differently would disagree
+ // with the folder the composer actually writes into.
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[account.work]\n"
+ "maildir=work\n"
+ "drafts=Drafts\n"
+ "\n"
+ "[account.personal]\n"
+ "maildir=personal\n"
+ "drafts=[Provider]/Bozze\n")));
+
+ const SavedQuery drafts = Config::builtinFilter(QStringLiteral("drafts"));
+ QVERIFY2(drafts.isGenerated(), "drafts is not a generated filter");
+
+ const QString all = config.resolvedQuery(drafts, QString());
+ QVERIFY(all.contains(QStringLiteral("path:\"work/Drafts/**\"")));
+ QVERIFY(all.contains(
+ QStringLiteral("path:\"personal/[Provider]/Bozze/**\"")));
+
+ // The account's OWN query, asserted on the STRING: the all-accounts query
+ // wrapped in this account's path returns exactly the right rows because
+ // path: is hierarchical, so a row count passes against the wrong thing.
+ const QString scoped = config.resolvedQuery(drafts, QStringLiteral("work"));
+ QCOMPARE(scoped, QStringLiteral("path:\"work/Drafts/**\""));
+ QVERIFY(!scoped.contains(QStringLiteral("personal")));
+}
+
+void TestConfig::theDraftsFilterMatchesNothingWithoutAFolder()
+{
+ // An empty query means "match everything" to notmuch, so a button labelled
+ // Drafts would show the whole Maildir.
+ QTemporaryDir dir;
+ Config config;
+ config.load(writeIni(dir, QStringLiteral(
+ "[account.work]\n"
+ "maildir=work\n")));
+
+ const SavedQuery drafts = Config::builtinFilter(QStringLiteral("drafts"));
+ QCOMPARE(config.resolvedQuery(drafts, QString()),
+ Config::matchNothingQuery());
+}
+
+void TestConfig::theDraftsFilterIsThreadedNotFlat()
+{
+ // Unlike Sent, and deliberately. Sent is flat because a thread would fold
+ // the user's own message back into the conversation it answers, which is
+ // item 63's finding. A draft reply belongs with its conversation for the
+ // same reason a trashed message does, so drafts follow trash here.
+ const SavedQuery drafts = Config::builtinFilter(QStringLiteral("drafts"));
+ QVERIFY2(!drafts.flat, "the drafts filter is flat, like Sent");
+
+ const SavedQuery sent = Config::builtinFilter(QStringLiteral("sent"));
+ QVERIFY2(sent.flat, "Sent stopped being flat, which item 63 requires");
+}
+
void TestConfig::theTrashFilterComposesPerAccount()
{
// Two accounts, one with a plain folder and one nested under a bracketed
@@ -1492,7 +1556,7 @@ void TestConfig::everyBuiltinFilterIsAKnownGenerator()
Config config;
const QList<SavedQuery> filters = config.builtinFilters();
- QCOMPARE(filters.size(), 5);
+ QCOMPARE(filters.size(), 6);
QStringList names;
for (const SavedQuery &filter : filters) {
@@ -1514,6 +1578,7 @@ void TestConfig::everyBuiltinFilterIsAKnownGenerator()
QStringLiteral("Inbox"),
QStringLiteral("Important"),
QStringLiteral("Sent"),
+ QStringLiteral("Drafts"),
QStringLiteral("Trash") }));
}
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 4c33302..3dbb227 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -276,6 +276,7 @@ private slots:
void aStartupAccountAlsoScopesASavedStartupQuery();
void aGeneratedStartupQueryActuallyRuns();
void everyBuiltinFilterButtonCarriesAnIconAndItsText();
+ void theDraftsButtonIsAbsentWithoutADraftsFolder();
void aQueryInTheMenuCanActuallyBeRun();
void theFourBuiltinFiltersAreOnTheRowInOrder();
void aFilterComposesWithTheSelectedAccount();
@@ -476,6 +477,7 @@ private slots:
// MainWindow hands it, so a Config written to a temporary INI is the whole
// fixture.
void aComposerOpensClean();
+ void ctrlWClosesTheComposer();
void theComposerSplitsItsToolbarByScope();
void ccAndBccHideBehindADisclosure();
void ccAndBccAreRevealedWhenTheyCarryAValue();
@@ -9572,6 +9574,32 @@ void TestMainWindow::aGeneratedStartupQueryActuallyRuns()
QCOMPARE(queryEdit->text(), config.allSentQuery());
}
+void TestMainWindow::theDraftsButtonIsAbsentWithoutADraftsFolder()
+{
+ // Item 138 follows item 103's rule: a folder filter with no folder to
+ // match is left out of the row rather than shown resolving to
+ // matchNothingQuery(). A button that can only ever report nothing is worse
+ // than no button, since it reads as "you have no drafts".
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString path = writeSentConfig(dir, {
+ {QStringLiteral("work"), QStringLiteral("Sent")},
+ });
+
+ Config config;
+ config.load(path);
+ MainWindow window(config);
+
+ QVERIFY2(!window.findChild<QAbstractButton *>(
+ QStringLiteral("draftsButton")),
+ "a Drafts button appeared for an account with no drafts folder");
+
+ // The guard: Sent IS configured here, so a change that dropped every
+ // filter button would otherwise pass the assertion above.
+ QVERIFY2(window.findChild<QAbstractButton *>(QStringLiteral("sentButton")),
+ "the Sent button is missing, so this test proves nothing");
+}
+
void TestMainWindow::everyBuiltinFilterButtonCarriesAnIconAndItsText()
{
// The filters are part of the application now, so they carry icons like the
@@ -9589,11 +9617,12 @@ void TestMainWindow::everyBuiltinFilterButtonCarriesAnIconAndItsText()
});
// A trash key too, or the Trash filter finds nothing and is skipped from
// the row entirely (item 103), leaving no trashButton for this loop to
- // find.
+ // find. Drafts behaves the same way since item 138.
{
QSettings s(path, QSettings::IniFormat);
s.beginGroup(QStringLiteral("account.work"));
s.setValue(QStringLiteral("trash"), QStringLiteral("Trash"));
+ s.setValue(QStringLiteral("drafts"), QStringLiteral("Drafts"));
s.endGroup();
}
Config config;
@@ -12420,6 +12449,36 @@ void TestMainWindow::removeAttachmentAppearsOnlyWithAttachments()
"Remove attachment is not offered with a file attached");
}
+void TestMainWindow::ctrlWClosesTheComposer()
+{
+ // Item 148. Ctrl+W closes a window in every application the user runs, and
+ // the composer bound nothing, so the only way out was the title bar.
+ //
+ // Scoped to the composer, not registered in KeyMap: Qt dispatches a
+ // WindowShortcut to the active window only, which is the same reason the
+ // formatting shortcuts are parented here rather than to the main window.
+ ComposeFixture fixture;
+ QVERIFY(fixture.build());
+ ComposeContext context = newContext();
+
+ QPointer<ComposeWindow> window =
+ new ComposeWindow(context, fixture.config(), fixture.mailRoot());
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ auto *close = window->findChild<QAction *>(QStringLiteral("compose_close"));
+ QVERIFY2(close, "the composer has no close action");
+ QVERIFY2(close->shortcut() == QKeySequence(QStringLiteral("Ctrl+W")),
+ qPrintable(QStringLiteral("the close action is bound to '%1', "
+ "not Ctrl+W")
+ .arg(close->shortcut().toString())));
+
+ close->trigger();
+
+ // WA_DeleteOnClose, so the window really goes rather than merely hiding.
+ QTRY_VERIFY_WITH_TIMEOUT(window.isNull(), 5000);
+}
+
void TestMainWindow::aComposerOpensClean()
{
ComposeFixture fixture;