krz/hutch

an ios client for sourcehut

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

v2: HutchTests/InboxViewModelTests.swift · raw

  1import Foundation
  2import Testing
  3@testable import Hutch
  4
  5struct InboxViewModelTests {
  6
  7    @Test
  8    func derivesRepositoryNameFromCommonPatchListSuffixes() {
  9        #expect(InboxViewModel.deriveRepositoryName(from: "hut-devel") == "hut")
 10        #expect(InboxViewModel.deriveRepositoryName(from: "git.patches") == "git")
 11        #expect(InboxViewModel.deriveRepositoryName(from: "discuss") == nil)
 12    }
 13
 14    @Test
 15    func parsesMailtoDraft() {
 16        let draft = ThreadViewModel.mailComposeDraft(
 17            from: "mailto:list@example.com?cc=author@example.com&subject=Re:%20PATCH&body=LGTM"
 18        )
 19
 20        #expect(draft?.recipients == ["list@example.com"])
 21        #expect(draft?.ccRecipients == ["author@example.com"])
 22        #expect(draft?.subject == "Re: PATCH")
 23        #expect(draft?.body == "LGTM")
 24    }
 25
 26    @Test
 27    func computesLocalUnreadStateFromLastViewedMarker() {
 28        let suiteName = "InboxViewModelTests-\(UUID().uuidString)"
 29        let defaults = UserDefaults(suiteName: suiteName)!
 30        defer { defaults.removePersistentDomain(forName: suiteName) }
 31
 32        let threadID = "list#message"
 33        let lastActivity = Date(timeIntervalSince1970: 2_000)
 34
 35        #expect(InboxReadStateStore.isUnread(threadID: threadID, lastActivityAt: lastActivity, defaults: defaults))
 36
 37        InboxReadStateStore.markViewed(Date(timeIntervalSince1970: 1_000), for: threadID, defaults: defaults)
 38        #expect(InboxReadStateStore.isUnread(threadID: threadID, lastActivityAt: lastActivity, defaults: defaults))
 39
 40        InboxReadStateStore.markViewed(Date(timeIntervalSince1970: 2_500), for: threadID, defaults: defaults)
 41        #expect(!InboxReadStateStore.isUnread(threadID: threadID, lastActivityAt: lastActivity, defaults: defaults))
 42    }
 43
 44    @Test
 45    func normalizesThreadSubjectsForDisplay() {
 46        let summary = InboxThreadSummary(
 47            rootEmailID: 1,
 48            rootMessageID: "message",
 49            threadRootEmailIDs: [1],
 50            threadRootMessageIDs: ["message"],
 51            listID: 2,
 52            listRID: "list",
 53            listName: "hut-devel",
 54            listOwner: Entity(canonicalName: "~owner"),
 55            subject: "Re: Fwd:   [PATCH] test: add parser  ",
 56            latestSender: Entity(canonicalName: "~sender"),
 57            lastActivityAt: Date(timeIntervalSince1970: 2_000),
 58            messageCount: 3,
 59            repo: "hut",
 60            containsPatch: true,
 61            isUnread: true
 62        )
 63
 64        #expect(summary.displaySubject == "[PATCH] test: add parser")
 65        #expect(summary.metadataLine.contains("~sender"))
 66        #expect(summary.metadataLine.contains("2 replies"))
 67    }
 68
 69    @Test
 70    func keepsDistinctThreadsDistinctByRootMessageID() {
 71        let baseList = InboxMailingListReference(
 72            id: 1,
 73            rid: "list",
 74            name: "hut-devel",
 75            owner: Entity(canonicalName: "~owner")
 76        )
 77
 78        let first = InboxThreadSummary(
 79            rootEmailID: 10,
 80            rootMessageID: "message-1",
 81            threadRootEmailIDs: [10],
 82            threadRootMessageIDs: ["message-1"],
 83            listID: baseList.id,
 84            listRID: baseList.rid,
 85            listName: baseList.name,
 86            listOwner: baseList.owner,
 87            subject: "[PATCH] test",
 88            latestSender: Entity(canonicalName: "~a"),
 89            lastActivityAt: Date(timeIntervalSince1970: 100),
 90            messageCount: 1,
 91            repo: "hut",
 92            containsPatch: true,
 93            isUnread: true
 94        )
 95        let second = InboxThreadSummary(
 96            rootEmailID: 11,
 97            rootMessageID: "message-2",
 98            threadRootEmailIDs: [11],
 99            threadRootMessageIDs: ["message-2"],
100            listID: baseList.id,
101            listRID: baseList.rid,
102            listName: baseList.name,
103            listOwner: baseList.owner,
104            subject: "[PATCH] test",
105            latestSender: Entity(canonicalName: "~b"),
106            lastActivityAt: Date(timeIntervalSince1970: 200),
107            messageCount: 2,
108            repo: "hut",
109            containsPatch: true,
110            isUnread: true
111        )
112
113        #expect(first.id != second.id)
114    }
115
116    @Test
117    func segmentsPatchBodyAndTreatsSignatureAsPlainText() {
118        let body = """
119        From: Christian Cleberg <hello@cleberg.net>
120
121        ---
122         test-patch.txt | 1 +
123         1 file changed, 1 insertion(+)
124         create mode 100644 test-patch.txt
125
126        diff --git a/test-patch.txt b/test-patch.txt
127        new file mode 100644
128        index 0000000..c7b6eed
129        --- /dev/null
130        +++ b/test-patch.txt
131        @@ -0,0 +1 @@
132        +test Wed Mar 18 23:19:03 CDT 2026
133        -- 
134        2.50.1 (Apple Git-155)
135        """
136
137        let segments = ThreadViewModel.segmentMessageBodyForTesting(body, isPatch: true)
138
139        #expect(segments.count == 3)
140
141        guard case let .plainText(leadingPlainText) = segments[0] else {
142            Issue.record("Expected first segment to be plain text")
143            return
144        }
145        #expect(leadingPlainText.contains("From: Christian Cleberg <hello@cleberg.net>"))
146        #expect(leadingPlainText.contains("---"))
147        #expect(leadingPlainText.contains(" test-patch.txt | 1 +"))
148        #expect(leadingPlainText.contains(" 1 file changed, 1 insertion(+)"))
149        #expect(leadingPlainText.contains(" create mode 100644 test-patch.txt"))
150
151        guard case let .diff(diff) = segments[1] else {
152            Issue.record("Expected second segment to be diff")
153            return
154        }
155        #expect(diff.contains("diff --git a/test-patch.txt b/test-patch.txt"))
156        #expect(diff.contains("--- /dev/null"))
157        #expect(diff.contains("+++ b/test-patch.txt"))
158        #expect(diff.contains("+test Wed Mar 18 23:19:03 CDT 2026"))
159        #expect(!diff.contains("-- \n2.50.1 (Apple Git-155)"))
160
161        guard case let .plainText(trailingPlainText) = segments[2] else {
162            Issue.record("Expected third segment to be plain text")
163            return
164        }
165        #expect(trailingPlainText.contains("--"))
166        #expect(trailingPlainText.contains("2.50.1 (Apple Git-155)"))
167    }
168}