krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v3.8.2: HutchTests/MailingListActivityTests.swift · raw

 1import Foundation
 2import Testing
 3@testable import Hutch
 4
 5struct MailingListActivityTests {
 6
 7    @Test
 8    func usesTheNewestArrivalOverTheRootTimestamp() {
 9        // The case that started this: sr.ht reports thread.updated seven seconds
10        // after the root email on a thread carrying four replies, so the fallback
11        // must lose to real arrival data.
12        let rootInsert = Date(timeIntervalSince1970: 1_000)
13        let newestReply = Date(timeIntervalSince1970: 5_000)
14        let activity = MailingListActivity(newestByRootEmailID: [42: newestReply])
15
16        #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == newestReply)
17    }
18
19    @Test
20    func fallsBackForThreadsOutsideTheScannedWindow() {
21        // Threads with nothing new are absent from the feed scan; they keep the
22        // root timestamp, which is older than any cutoff and so reads as read.
23        let rootInsert = Date(timeIntervalSince1970: 1_000)
24        let activity = MailingListActivity(newestByRootEmailID: [:])
25
26        #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == rootInsert)
27    }
28
29    @Test
30    func neverGoesBackwardsFromTheFallback() {
31        // A root inserted after the newest scanned reply must not age the thread
32        // backwards.
33        let rootInsert = Date(timeIntervalSince1970: 9_000)
34        let staleReply = Date(timeIntervalSince1970: 5_000)
35        let activity = MailingListActivity(newestByRootEmailID: [42: staleReply])
36
37        #expect(activity.lastActivity(rootEmailID: 42, fallback: rootInsert) == rootInsert)
38    }
39
40    @Test
41    func tracksThreadsIndependently() {
42        let activity = MailingListActivity(newestByRootEmailID: [
43            1: Date(timeIntervalSince1970: 5_000),
44            2: Date(timeIntervalSince1970: 7_000)
45        ])
46        let fallback = Date(timeIntervalSince1970: 1_000)
47
48        #expect(activity.lastActivity(rootEmailID: 1, fallback: fallback) == Date(timeIntervalSince1970: 5_000))
49        #expect(activity.lastActivity(rootEmailID: 2, fallback: fallback) == Date(timeIntervalSince1970: 7_000))
50        #expect(activity.lastActivity(rootEmailID: 3, fallback: fallback) == fallback)
51    }
52
53    @Test
54    func newMailInAnOldThreadReadsAsUnread() {
55        let suiteName = "MailingListActivityTests-\(UUID().uuidString)"
56        let defaults = UserDefaults(suiteName: suiteName)!
57        defer { defaults.removePersistentDomain(forName: suiteName) }
58
59        let signIn = Date(timeIntervalSince1970: 5_000)
60        InboxReadStateStore.establishBaselineIfNeeded(now: signIn, defaults: defaults)
61
62        // A thread rooted long before sign-in, with a reply after it. Keyed on
63        // thread.updated this reads as read, which was the bug.
64        let rootInsert = Date(timeIntervalSince1970: 1_000)
65        let replyAfterSignIn = Date(timeIntervalSince1970: 6_000)
66        let activity = MailingListActivity(newestByRootEmailID: [42: replyAfterSignIn])
67        let lastActivityAt = activity.lastActivity(rootEmailID: 42, fallback: rootInsert)
68
69        #expect(
70            InboxReadStateStore.isUnread(
71                threadID: "list#old thread",
72                lastActivityAt: lastActivityAt,
73                defaults: defaults
74            )
75        )
76    }
77}