| @@ -31,7 +31,7 @@ private func decode(_ json: String) throws -> InboxNotification { |
| 31 | 31 | private let unreadBuild = """ |
| 32 | 32 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
| 33 | 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 | 37 | struct NotificationDecodingTests { |
| @@ -48,7 +48,7 @@ struct NotificationDecodingTests { |
| 48 | 48 | let n = try decode(""" |
| 49 | 49 | {"id":15,"repo":"krz/gitbay","kind":"build","actor":"ci",\ |
| 50 | 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 | 53 | #expect(n.readAt != nil) |
| 54 | 54 | #expect(n.isUnread == false) |
| @@ -57,14 +57,14 @@ struct NotificationDecodingTests { |
| 57 | 57 | @Test func everyKindRoutesToItsScreen() throws { |
| 58 | 58 | let issue = try decode(""" |
| 59 | 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 | 62 | #expect(issue.destination == .issue(repo: "krz/gitbay", number: 168)) |
| 63 | 63 | |
| 64 | 64 | // The segment is "mrs", not "merge_requests". |
| 65 | 65 | let mr = try decode(""" |
| 66 | 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 | 69 | #expect(mr.destination == .mr(repo: "krz/gitbay", number: 282)) |
| 70 | 70 | |
| @@ -79,7 +79,7 @@ struct NotificationDecodingTests { |
| 79 | 79 | "too/short", "krz/gitbay/issues/1/extra", ""] { |
| 80 | 80 | let n = try decode(""" |
| 81 | 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 | 84 | #expect(n.destination == nil, "\(path) should not route") |
| 85 | 85 | } |
| @@ -90,7 +90,7 @@ struct NotificationDecodingTests { |
| 90 | 90 | @Test func theDestinationRepoComesFromThePath() throws { |
| 91 | 91 | let n = try decode(""" |
| 92 | 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 | 95 | #expect(n.destination == .issue(repo: "krz/gitbay", number: 7)) |
| 96 | 96 | } |
| @@ -103,6 +103,10 @@ struct NotificationsViewModelTests { |
| 103 | 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 | 110 | @Test func theDefaultListingIsUnreadOnly() async throws { |
| 107 | 111 | let (client, stub) = try makeClient() |
| 108 | 112 | stub.enqueue(.init(status: 200, json: page)) |
| @@ -128,6 +132,23 @@ struct NotificationsViewModelTests { |
| 128 | 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 | 152 | @Test func markingOneReadSendsItsId() async throws { |
| 132 | 153 | let (client, stub) = try makeClient() |
| 133 | 154 | stub.enqueue(.init(status: 200, json: page)) |