summaryrefslogtreecommitdiff
path: root/HutchTests/MailingListActivityTests.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-15 23:27:03 -0500
committerGitHub <[email protected]>2026-07-15 23:27:03 -0500
commit0ad430c79eedac4140680e3aaca238969ac711b1 (patch)
treee4928ae266acb2282a12e8212b82635890f50089 /HutchTests/MailingListActivityTests.swift
parent922502a2c74c66034f3ec2db6612f2a36d242042 (diff)
parent97e8b49fcb56751fa0e80a332484db8252753417 (diff)
downloadhutch-0ad430c79eedac4140680e3aaca238969ac711b1.tar.gz
hutch-0ad430c79eedac4140680e3aaca238969ac711b1.tar.bz2
hutch-0ad430c79eedac4140680e3aaca238969ac711b1.zip
Merge pull request #5 from zerolabsco/inbox-unread-baseline
Start a new account at zero unread
Diffstat (limited to 'HutchTests/MailingListActivityTests.swift')
-rw-r--r--HutchTests/MailingListActivityTests.swift77
1 files changed, 77 insertions, 0 deletions
diff --git a/HutchTests/MailingListActivityTests.swift b/HutchTests/MailingListActivityTests.swift
new file mode 100644
index 0000000..46208cb
--- /dev/null
+++ b/HutchTests/MailingListActivityTests.swift
@@ -0,0 +1,77 @@
+import Foundation
+import Testing
+@testable import Hutch
+
+struct MailingListActivityTests {
+
+ @Test
+ func usesTheNewestArrivalOverTheRootTimestamp() {
+ // The case that started this: sr.ht reports thread.updated seven seconds
+ // after the root email on a thread carrying four replies, so the fallback
+ // must lose to real arrival data.
+ let rootInsert = Date(timeIntervalSince1970: 1_000)
+ let newestReply = Date(timeIntervalSince1970: 5_000)
+ let activity = MailingListActivity(newestByRootEmailID: [42: newestReply])
+
+ #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == newestReply)
+ }
+
+ @Test
+ func fallsBackForThreadsOutsideTheScannedWindow() {
+ // Threads with nothing new are absent from the feed scan; they keep the
+ // root timestamp, which is older than any cutoff and so reads as read.
+ let rootInsert = Date(timeIntervalSince1970: 1_000)
+ let activity = MailingListActivity(newestByRootEmailID: [:])
+
+ #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == rootInsert)
+ }
+
+ @Test
+ func neverGoesBackwardsFromTheFallback() {
+ // A root inserted after the newest scanned reply must not age the thread
+ // backwards.
+ let rootInsert = Date(timeIntervalSince1970: 9_000)
+ let staleReply = Date(timeIntervalSince1970: 5_000)
+ let activity = MailingListActivity(newestByRootEmailID: [42: staleReply])
+
+ #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == rootInsert)
+ }
+
+ @Test
+ func tracksThreadsIndependently() {
+ let activity = MailingListActivity(newestByRootEmailID: [
+ 1: Date(timeIntervalSince1970: 5_000),
+ 2: Date(timeIntervalSince1970: 7_000)
+ ])
+ let fallback = Date(timeIntervalSince1970: 1_000)
+
+ #expect(activity.lastActivity(rootEmailID: 1, fallback: fallback) == Date(timeIntervalSince1970: 5_000))
+ #expect(activity.lastActivity(rootEmailID: 2, fallback: fallback) == Date(timeIntervalSince1970: 7_000))
+ #expect(activity.lastActivity(rootEmailID: 3, fallback: fallback) == fallback)
+ }
+
+ @Test
+ func newMailInAnOldThreadReadsAsUnread() {
+ let suiteName = "MailingListActivityTests-\(UUID().uuidString)"
+ let defaults = UserDefaults(suiteName: suiteName)!
+ defer { defaults.removePersistentDomain(forName: suiteName) }
+
+ let signIn = Date(timeIntervalSince1970: 5_000)
+ InboxReadStateStore.establishBaselineIfNeeded(now: signIn, defaults: defaults)
+
+ // A thread rooted long before sign-in, with a reply after it. Keyed on
+ // thread.updated this reads as read, which was the bug.
+ let rootInsert = Date(timeIntervalSince1970: 1_000)
+ let replyAfterSignIn = Date(timeIntervalSince1970: 6_000)
+ let activity = MailingListActivity(newestByRootEmailID: [42: replyAfterSignIn])
+ let lastActivityAt = activity.lastActivity(rootEmailID: 42, fallback: rootInsert)
+
+ #expect(
+ InboxReadStateStore.isUnread(
+ threadID: "list#old thread",
+ lastActivityAt: lastActivityAt,
+ defaults: defaults
+ )
+ )
+ }
+}