aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-25 09:12:29 +0200
committerDanilo M. <danix@danix.xyz>2026-08-25 09:12:29 +0200
commit0a26961f9a7ae6ab98051e182b92e64165758cf1 (patch)
tree5d76949bf34ab581826f0f699d60e0fc45b3e175 /tests
parent8399a2652584e348ba73f7059d9e178958855897 (diff)
downloadqtmaildir-0a26961f9a7ae6ab98051e182b92e64165758cf1.tar.gz
qtmaildir-0a26961f9a7ae6ab98051e182b92e64165758cf1.zip
fix(drafts): list drafts as messages, not threadssignatures
The Drafts filter shipped threaded in item 138, reasoning that a draft reply belongs with the conversation it answers. That reasoning cost the feature: a thread row stands for its first matched message, which for a draft reply is the message being replied to, so the draft itself had no row of its own and double-clicking the conversation opened nothing. Reversed with the user. Drafts now follows Sent; Trash deliberately does not, since a deleted message still belongs to its conversation and nothing there has to be reachable for editing. The view mode was decided in three places that each compared against "sent" and had to agree: builtinFilter(), the reader that reapplies the mode, and the writer that skips storing what the generator implies. generatorIsFlat() is now the one closed set they share, and builtinFilter() sets flat from it rather than inside a branch so the set cannot drift from the labels. Setting only the branch would have looked correct. Its save/load pair survives by accident, because the writer's skip knew only "sent" and so would have stored the key for drafts. The gap is the reader's fallback, for a file carrying no flat key at all: an older build, a migration or a hand edit comes back threaded against a flat button, and the next save persists the disagreement. theDraftsFilterIsThreadedNotFlat is inverted rather than deleted, keeping its history, and now also pins Trash as threaded. The round trip is covered by extending aGeneratedEntryWritesNoRedundantKeys, which already asserted that property for Sent. Mutation-checked: reverting generatorIsFlat() to "sent" alone fails both. Suite 37 of 38; undoMovesTheMessageBack is item 136, pre-existing and on an unrelated path. Closes item 159. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUQS6n3cmsFrsjCNmwNtf8
Diffstat (limited to 'tests')
-rw-r--r--tests/test_config.cpp36
1 files changed, 27 insertions, 9 deletions
diff --git a/tests/test_config.cpp b/tests/test_config.cpp
index 17b8e1d..e69a073 100644
--- a/tests/test_config.cpp
+++ b/tests/test_config.cpp
@@ -122,7 +122,7 @@ private slots:
void anAccountWithoutATrashFolderWarns();
void theDraftsFilterComposesPerAccount();
void theDraftsFilterMatchesNothingWithoutAFolder();
- void theDraftsFilterIsThreadedNotFlat();
+ void theDraftsFilterIsFlatLikeSent();
void theTrashFilterComposesPerAccount();
void theTrashFilterMatchesNothingWithoutAFolder();
void anAccountWithoutASendCommandIsReceiveOnly();
@@ -1076,17 +1076,24 @@ void TestConfig::theDraftsFilterMatchesNothingWithoutAFolder()
Config::matchNothingQuery());
}
-void TestConfig::theDraftsFilterIsThreadedNotFlat()
+void TestConfig::theDraftsFilterIsFlatLikeSent()
{
- // 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.
+ // Item 138 shipped this THREADED, reasoning that a draft reply belongs
+ // with the conversation it answers. Item 159 reversed it on what that
+ // cost: a thread row stands for its first MATCHED message, which for a
+ // draft reply is the message being replied TO, so the draft had no row of
+ // its own and double-clicking the conversation opened nothing.
const SavedQuery drafts = Config::builtinFilter(QStringLiteral("drafts"));
- QVERIFY2(!drafts.flat, "the drafts filter is flat, like Sent");
+ QVERIFY2(drafts.flat, "the drafts filter went back to threaded, so a draft "
+ "reply has no row of its own (item 159)");
const SavedQuery sent = Config::builtinFilter(QStringLiteral("sent"));
QVERIFY2(sent.flat, "Sent stopped being flat, which item 63 requires");
+
+ // Trash deliberately did NOT follow. A deleted message still belongs to
+ // its conversation, and nothing has to be reachable for editing there.
+ const SavedQuery trash = Config::builtinFilter(QStringLiteral("trash"));
+ QVERIFY2(!trash.flat, "trash became flat; only sent and drafts should be");
}
void TestConfig::theTrashFilterComposesPerAccount()
@@ -2357,6 +2364,7 @@ void TestConfig::aGeneratedEntryWritesNoRedundantKeys()
"version": 1,
"queries": [
{ "name": "Sent", "generated": "sent", "pinned": true },
+ { "name": "Drafts", "generated": "drafts", "pinned": true },
{ "name": "Inbox", "query": "tag:inbox", "pinned": true }
]
})"));
@@ -2381,18 +2389,28 @@ void TestConfig::aGeneratedEntryWritesNoRedundantKeys()
QVERIFY2(!sent.contains(QStringLiteral("flat")),
"the sent generator implies flat; storing it says nothing");
+ // Drafts is the second flat generator (item 159) and must be skipped by
+ // the same rule, not by a second one that could disagree with it.
+ const QJsonObject drafts = array.at(1).toObject();
+ QCOMPARE(drafts.value(QStringLiteral("generated")).toString(),
+ QStringLiteral("drafts"));
+ QVERIFY2(!drafts.contains(QStringLiteral("flat")),
+ "the drafts generator implies flat; storing it says nothing");
+
// The ordinary entry is untouched by any of that.
- const QJsonObject inbox = array.at(1).toObject();
+ const QJsonObject inbox = array.at(2).toObject();
QCOMPARE(inbox.value(QStringLiteral("query")).toString(),
QStringLiteral("tag:inbox"));
// And it all still reads back the same.
Config reloaded;
reloaded.load(path);
- QCOMPARE(reloaded.savedQueries().size(), 2);
+ QCOMPARE(reloaded.savedQueries().size(), 3);
QVERIFY(reloaded.savedQueries().at(0).isGenerated());
QVERIFY2(reloaded.savedQueries().at(0).flat,
"flat must come back from the generator, not from the file");
+ QVERIFY2(reloaded.savedQueries().at(1).flat,
+ "drafts must come back flat too, from the same rule");
}
void TestConfig::anAccountWithoutASendCommandIsReceiveOnly()