aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_mainwindow.cpp196
1 files changed, 196 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 1c54ebb..b7f7846 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -438,6 +438,10 @@ private slots:
void anUnknownExternalStateClearsNothing();
void aSuccessfulCronSyncDrainsTheEditedAccounts();
void aCronSyncDoesNotClearAnEditMadeWhileItRan();
+ void syncNarrowsToTheSelectedAccount();
+ void syncCoversEveryAccountWhenAllIsSelected();
+ void aNarrowedSyncStillCarriesAnEditFromAnotherAccount();
+ void theStatusLineNamesWhatASyncWillCover();
void everyActionCarriesAnIcon();
void everyActionIsReachableFromAMenu();
@@ -7496,6 +7500,198 @@ void TestMainWindow::aCronSyncDoesNotClearAnEditMadeWhileItRan()
"an edit made after the sync ended was swallowed by it");
}
+// Item 101.
+
+namespace {
+
+/// Writes a config with two accounts, each naming its own sync channel, so a
+/// narrowed run can be told from a full one by the channel list alone.
+void loadTwoAccountConfig(Config &config, const QTemporaryDir &dir,
+ const QString &logPath)
+{
+ const QString path = dir.filePath(QStringLiteral("qtmaildir.conf"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write(QStringLiteral("[sync]\nlog=%1\nstatus=%2\n\n"
+ "[account.work]\n"
+ "maildir=work-mail\n"
+ "channel=work-channel\n\n"
+ "[account.personal]\n"
+ "maildir=personal-mail\n"
+ "channel=personal-channel\n")
+ .arg(logPath, dir.filePath(QStringLiteral("no-status.json")))
+ .toUtf8());
+ file.close();
+
+ config.load(path);
+ QCOMPARE(config.accounts().size(), 2);
+}
+
+/// Records one edit against \p accountKey, by giving the model a thread tagged
+/// for that account and sending a tag change over it. This is the route a real
+/// edit takes, so m_editedAccounts is populated the way production populates it.
+void recordEditForAccount(MainWindow &window, const QString &threadId,
+ const QString &accountKey)
+{
+ auto *model = window.findChild<ThreadListModel *>();
+ QVERIFY(model);
+ ThreadSummary thread;
+ thread.threadId = threadId;
+ thread.subject = QStringLiteral("Subject");
+ thread.totalCount = 1;
+ thread.firstMessageId = threadId + QStringLiteral("-m1");
+ thread.tags = { QStringLiteral("account-") + accountKey,
+ QStringLiteral("inbox") };
+ model->appendBatch({ thread });
+ QCOMPARE(model->accountKeysForThread(threadId), QStringList{ accountKey });
+
+ QVERIFY(QMetaObject::invokeMethod(
+ &window, "sendThreadTagChange",
+ Q_ARG(QStringList, QStringList{ threadId }),
+ Q_ARG(QStringList, QStringList{ QStringLiteral("flagged") }),
+ Q_ARG(QStringList, QStringList{}),
+ Q_ARG(QString, QStringLiteral("Flag"))));
+}
+
+/// QVERIFY2 expands to a bare `return`, so the invoke check cannot live in a
+/// value-returning helper. Out-param instead, called through the macro below.
+void readSyncChannels(MainWindow &window, QStringList &channels)
+{
+ QVERIFY2(QMetaObject::invokeMethod(&window, "pendingSyncChannels",
+ Q_RETURN_ARG(QStringList, channels)),
+ "pendingSyncChannels() could not be invoked");
+}
+
+#define SYNC_CHANNELS(window, out) \
+ QStringList out; \
+ readSyncChannels((window), (out))
+
+} // namespace
+
+void TestMainWindow::syncNarrowsToTheSelectedAccount()
+{
+ // The user's decision on item 101: one Sync action, steered by the account
+ // dropdown. Selecting an account and pressing Sync collects that account
+ // only, rather than every channel.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ // The guard, and it matters: an empty list is what a FULL sync looks like,
+ // so a test starting from a narrowed state could not tell the fix from a
+ // window that had never widened. Nothing is selected and nothing is edited,
+ // so this must be the full-fetch signal before the gesture.
+ SYNC_CHANNELS(window, before);
+ QVERIFY2(before.isEmpty(),
+ "a fresh window with All accounts selected did not ask for a "
+ "full fetch");
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+
+ SYNC_CHANNELS(window, narrowed);
+ QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") });
+}
+
+void TestMainWindow::syncCoversEveryAccountWhenAllIsSelected()
+{
+ // The other half of the same rule, and the one that keeps item 49's
+ // property: All accounts means a full fetch, which mailsync.sh turns into
+ // mbsync -a. Asserted after a narrowing so it proves the selection is read
+ // each time rather than latched once.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+ SYNC_CHANNELS(window, narrowed);
+ QCOMPARE(narrowed, QStringList{ QStringLiteral("work-channel") });
+
+ window.selectAccountForTesting(QString());
+
+ SYNC_CHANNELS(window, widened);
+ QVERIFY2(widened.isEmpty(),
+ "going back to All accounts left the run narrowed");
+}
+
+void TestMainWindow::aNarrowedSyncStillCarriesAnEditFromAnotherAccount()
+{
+ // Item 101's standing constraint, and the reason the selection is a UNION
+ // with the pending set rather than a replacement for it. Looking at one
+ // account while having edited another is ordinary, and a run that dropped
+ // the other account's channel would strand that edit with nothing on
+ // screen to say so.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ // Edit in personal, then go and look at work.
+ recordEditForAccount(window, QStringLiteral("t1"),
+ QStringLiteral("personal"));
+ SYNC_CHANNELS(window, edited);
+ QCOMPARE(edited, QStringList{ QStringLiteral("personal-channel") });
+
+ window.selectAccountForTesting(QStringLiteral("work"));
+
+ // Both: work because it is on screen, personal because it owes a write.
+ const QStringList expected{ QStringLiteral("personal-channel"),
+ QStringLiteral("work-channel") };
+ SYNC_CHANNELS(window, both);
+ QCOMPARE(both, expected);
+}
+
+void TestMainWindow::theStatusLineNamesWhatASyncWillCover()
+{
+ // Item 101's visibility half. The run was already account-aware before this
+ // item and the user could not tell, since a narrowed run and a full one
+ // showed the same three dots. Asserted on the generated STRING, which has a
+ // right answer, rather than on the label after a real run, which would be a
+ // test of QProcess timing.
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ const QString logPath = dir.filePath(QStringLiteral("mailsync.log"));
+ writeSyncLog(logPath, QStringLiteral("OK"));
+
+ Config config;
+ loadTwoAccountConfig(config, dir, logPath);
+ MainWindow window(config);
+
+ QString text;
+
+ // Empty is the full-fetch signal, so it must not read as a channel list.
+ QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText",
+ Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, QStringList{})));
+ QCOMPARE(text, QStringLiteral("Syncing all accounts..."));
+
+ QVERIFY(QMetaObject::invokeMethod(
+ &window, "syncStartedText", Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, QStringList{ QStringLiteral("work-channel") })));
+ QCOMPARE(text, QStringLiteral("Syncing work-channel..."));
+
+ const QStringList two{ QStringLiteral("personal-channel"),
+ QStringLiteral("work-channel") };
+ QVERIFY(QMetaObject::invokeMethod(&window, "syncStartedText",
+ Q_RETURN_ARG(QString, text),
+ Q_ARG(QStringList, two)));
+ QCOMPARE(text,
+ QStringLiteral("Syncing personal-channel, work-channel..."));
+}
+
// Items 56 and 57.
void TestMainWindow::everyActionIsReachableFromAMenu()