| @@ -31,7 +31,7 @@ private func decode(_ json: String) throws -> InboxNotification { |
| 31 | private let unreadBuild = """ |
31 | private let unreadBuild = """ |
| 32 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
32 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
| 33 | "summary":"build 966 failed","path":"krz/gitbay/builds/966",\ |
33 | "summary":"build 966 failed","path":"krz/gitbay/builds/966",\ |
| 34 | "created_at":"2026-09-06T22:14:46Z"} |
34 | "created_at":"2026-09-06T22:14:46.000Z"} |
| 35 | """ |
35 | """ |
| 36 | |
36 | |
| 37 | struct NotificationDecodingTests { |
37 | struct NotificationDecodingTests { |
| @@ -48,7 +48,7 @@ struct NotificationDecodingTests { |
| 48 | let n = try decode(""" |
48 | let n = try decode(""" |
| 49 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
49 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
| 50 | "summary":"x","path":"krz/gitbay/builds/966",\ |
50 | "summary":"x","path":"krz/gitbay/builds/966",\ |
| 51 | "created_at":"2026-09-06T22:14:46Z","read_at":"2026-09-06T22:20:24Z"} |
51 | "created_at":"2026-09-06T22:14:46.000Z","read_at":"2026-09-06T22:20:24.000Z"} |
| 52 | """) |
52 | """) |
| 53 | #expect(n.readAt != nil) |
53 | #expect(n.readAt != nil) |
| 54 | #expect(n.isUnread == false) |
54 | #expect(n.isUnread == false) |
| @@ -57,14 +57,14 @@ struct NotificationDecodingTests { |
| 57 | @Test func everyKindRoutesToItsScreen() throws { |
57 | @Test func everyKindRoutesToItsScreen() throws { |
| 58 | let issue = try decode(""" |
58 | let issue = try decode(""" |
| 59 | {"id":1,"repo":"krz/gitbay","kind":"issue","actor":"cmc","summary":"x",\ |
59 | {"id":1,"repo":"krz/gitbay","kind":"issue","actor":"cmc","summary":"x",\ |
| 60 | "path":"krz/gitbay/issues/168","created_at":"2026-09-06T22:14:46Z"} |
60 | "path":"krz/gitbay/issues/168","created_at":"2026-09-06T22:14:46.000Z"} |
| 61 | """) |
61 | """) |
| 62 | #expect(issue.destination == .issue(repo: "krz/gitbay", number: 168)) |
62 | #expect(issue.destination == .issue(repo: "krz/gitbay", number: 168)) |
| 63 | |
63 | |
| 64 | // The segment is "mrs", not "merge_requests". |
64 | // The segment is "mrs", not "merge_requests". |
| 65 | let mr = try decode(""" |
65 | let mr = try decode(""" |
| 66 | {"id":2,"repo":"krz/gitbay","kind":"mr","actor":"cmc","summary":"x",\ |
66 | {"id":2,"repo":"krz/gitbay","kind":"mr","actor":"cmc","summary":"x",\ |
| 67 | "path":"krz/gitbay/mrs/282","created_at":"2026-09-06T22:14:46Z"} |
67 | "path":"krz/gitbay/mrs/282","created_at":"2026-09-06T22:14:46.000Z"} |
| 68 | """) |
68 | """) |
| 69 | #expect(mr.destination == .mr(repo: "krz/gitbay", number: 282)) |
69 | #expect(mr.destination == .mr(repo: "krz/gitbay", number: 282)) |
| 70 | |
70 | |
| @@ -79,7 +79,7 @@ struct NotificationDecodingTests { |
| 79 | "too/short", "krz/gitbay/issues/1/extra", ""] { |
79 | "too/short", "krz/gitbay/issues/1/extra", ""] { |
| 80 | let n = try decode(""" |
80 | let n = try decode(""" |
| 81 | {"id":1,"repo":"krz/gitbay","kind":"issue","actor":"cmc","summary":"x",\ |
81 | {"id":1,"repo":"krz/gitbay","kind":"issue","actor":"cmc","summary":"x",\ |
| 82 | "path":"\(path)","created_at":"2026-09-06T22:14:46Z"} |
82 | "path":"\(path)","created_at":"2026-09-06T22:14:46.000Z"} |
| 83 | """) |
83 | """) |
| 84 | #expect(n.destination == nil, "\(path) should not route") |
84 | #expect(n.destination == nil, "\(path) should not route") |
| 85 | } |
85 | } |
| @@ -90,7 +90,7 @@ struct NotificationDecodingTests { |
| 90 | @Test func theDestinationRepoComesFromThePath() throws { |
90 | @Test func theDestinationRepoComesFromThePath() throws { |
| 91 | let n = try decode(""" |
91 | let n = try decode(""" |
| 92 | {"id":1,"repo":"other/repo","kind":"issue","actor":"cmc","summary":"x",\ |
92 | {"id":1,"repo":"other/repo","kind":"issue","actor":"cmc","summary":"x",\ |
| 93 | "path":"krz/gitbay/issues/7","created_at":"2026-09-06T22:14:46Z"} |
93 | "path":"krz/gitbay/issues/7","created_at":"2026-09-06T22:14:46.000Z"} |
| 94 | """) |
94 | """) |
| 95 | #expect(n.destination == .issue(repo: "krz/gitbay", number: 7)) |
95 | #expect(n.destination == .issue(repo: "krz/gitbay", number: 7)) |
| 96 | } |
96 | } |
| @@ -103,6 +103,10 @@ struct NotificationsViewModelTests { |
| 103 | {"protocol_version":1,"data":{"items":[\(unreadBuild)]},"exit_code":0} |
103 | {"protocol_version":1,"data":{"items":[\(unreadBuild)]},"exit_code":0} |
| 104 | """ |
104 | """ |
| 105 | |
105 | |
| |
106 | private let pageWithCursor = """ |
| |
107 | {"protocol_version":1,"data":{"items":[\(unreadBuild)],"next":"c1"},"exit_code":0} |
| |
108 | """ |
| |
109 | |
| 106 | @Test func theDefaultListingIsUnreadOnly() async throws { |
110 | @Test func theDefaultListingIsUnreadOnly() async throws { |
| 107 | let (client, stub) = try makeClient() |
111 | let (client, stub) = try makeClient() |
| 108 | stub.enqueue(.init(status: 200, json: page)) |
112 | stub.enqueue(.init(status: 200, json: page)) |
| @@ -128,6 +132,23 @@ struct NotificationsViewModelTests { |
| 128 | #expect(argvFrom(try #require(stub.seen.last).url).contains("--all")) |
132 | #expect(argvFrom(try #require(stub.seen.last).url).contains("--all")) |
| 129 | } |
133 | } |
| 130 | |
134 | |
| |
135 | /// A filter change reloads rather than pages, so it must not carry |
| |
136 | /// forward a cursor from whatever page was on screen. |
| |
137 | @Test func aFilterChangeDoesNotCarryAStaleCursor() async throws { |
| |
138 | let (client, stub) = try makeClient() |
| |
139 | stub.enqueue(.init(status: 200, json: pageWithCursor)) |
| |
140 | let model = NotificationsViewModel(client: client) |
| |
141 | await model.load() |
| |
142 | #expect(model.list.hasMore) |
| |
143 | |
| |
144 | stub.enqueue(.init(status: 200, json: page)) |
| |
145 | model.showAll = true |
| |
146 | try await Task.sleep(for: .milliseconds(150)) |
| |
147 | |
| |
148 | let argv = argvFrom(try #require(stub.seen.last).url) |
| |
149 | #expect(argv.contains("--cursor") == false) |
| |
150 | } |
| |
151 | |
| 131 | @Test func markingOneReadSendsItsId() async throws { |
152 | @Test func markingOneReadSendsItsId() async throws { |
| 132 | let (client, stub) = try makeClient() |
153 | let (client, stub) = try makeClient() |
| 133 | stub.enqueue(.init(status: 200, json: page)) |
154 | stub.enqueue(.init(status: 200, json: page)) |