diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_config.cpp | 67 | ||||
| -rw-r--r-- | tests/test_mainwindow.cpp | 61 |
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; |
