krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2.1: 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}