gitbayTests/MRLabelTests.swift
144 lines · 5546 bytes
14 symbols in this file
1import Foundation
2import Testing
3@testable import gitbay
4
5private func makeClient() throws -> (GitbayClient, StubProtocol.Box) {
6 let box = StubProtocol.box()
7 let client = GitbayClient(
8 instance: try GitbayInstance(url: "https://gitbay.org"),
9 token: "test-token",
10 session: box.session()
11 )
12 return (client, box)
13}
14
15private func mrShow(labels: String?) -> String {
16 let field = labels.map { #","labels":\#($0)"# } ?? ""
17 return """
18 {"protocol_version":1,"data":{"number":7,"title":"t","state":"open","author":"cmc",\
19 "source":"feature","target_ref":"main","head_sha":"aa",\
20 "created_at":"2026-09-22T04:40:16.000Z"\(field)},"exit_code":0}
21 """
22}
23
24private let okJSON = #"{"protocol_version":1,"exit_code":0}"#
25
26private func argvOf(_ seen: StubProtocol.Seen) throws -> [String] {
27 let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any])
28 return try #require(body["argv"] as? [String])
29}
30
31@MainActor
32struct MRLabelTests {
33
34 @Test func mrShowCarriesItsLabels() async throws {
35 let (client, stub) = try makeClient()
36 stub.enqueue(.init(status: 200, json: mrShow(labels: #"["bug","ios"]"#), match: "argv=show"))
37 let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7)
38
39 await model.load()
40
41 #expect(try #require(model.state.value).labels == ["bug", "ios"])
42 }
43
44 /// The server omits the key when there are none.
45 @Test func noLabelsKeyMeansNoLabels() async throws {
46 let (client, stub) = try makeClient()
47 stub.enqueue(.init(status: 200, json: mrShow(labels: nil), match: "argv=show"))
48 let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7)
49
50 await model.load()
51
52 #expect(try #require(model.state.value).labels.isEmpty)
53 }
54
55 @Test func addingAndRemovingSendMrLabel() async throws {
56 let (client, stub) = try makeClient()
57 let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7)
58
59 stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
60 stub.enqueue(.init(status: 200, json: mrShow(labels: #"["bug"]"#), match: "argv=show"))
61 await model.addLabel("bug")
62 stub.enqueue(.init(status: 200, json: okJSON, match: "cmd"))
63 stub.enqueue(.init(status: 200, json: mrShow(labels: nil), match: "argv=show"))
64 await model.removeLabel("bug")
65
66 let writes = try stub.seen.filter { $0.method == "POST" }.map(argvOf)
67 #expect(writes == [
68 ["mr", "label", "krz/gitbay", "7", "--add", "bug"],
69 ["mr", "label", "krz/gitbay", "7", "--remove", "bug"],
70 ])
71 #expect(model.actionError == nil)
72 }
73
74 /// Colours come from the repository's label set, read once.
75 @Test func labelColoursAreReadOnce() async throws {
76 let (client, stub) = try makeClient()
77 stub.enqueue(.init(status: 200, json: """
78 {"protocol_version":1,"data":[{"name":"bug","color":"#d73a4a","issues":2}],"exit_code":0}
79 """, match: "argv=label&argv=list"))
80 let model = MRDetailViewModel(client: client, repoPath: "krz/gitbay", number: 7)
81
82 await model.loadLabels()
83 await model.loadLabels()
84
85 #expect(stub.seen.count == 1)
86 #expect(stub.seen[0].url.query() == "argv=label&argv=list&argv=krz/gitbay")
87 #expect(model.colors.hex("bug") == LabelPalette.hex(name: "bug", stored: "#d73a4a"))
88 }
89}
90
91struct MRLabelFilterFlagTests {
92
93 /// Same position as in IssueFilter: after --search, before --author.
94 @Test func aLabelSendsTheLabelFlag() {
95 var filter = MRFilter()
96 filter.search = "gate"
97 filter.label = "bug"
98 filter.author = "cmc"
99 #expect(filter.flags() == ["--state", "open", "--search", "gate", "--label", "bug", "--author", "cmc"])
100 #expect(filter.isNarrowed)
101 }
102
103 @Test func noLabelSendsNoFlag() {
104 #expect(!MRFilter().flags().contains("--label"))
105 }
106}
107
108@MainActor
109struct MRLabelFilterTests {
110
111 @Test func pickingALabelReloadsWithIt() async throws {
112 let (client, stub) = try makeClient()
113 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"items":[]},"exit_code":0}"#))
114 let model = MRListViewModel(client: client, repoPath: "krz/gitbay")
115 await model.load()
116
117 stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"items":[]},"exit_code":0}"#))
118 model.filter.label = "bug"
119 await until {
120 guard stub.seen.count == 2 else { return false }
121 if case .loading = model.state { return false }
122 return true
123 }
124
125 let query = try #require(stub.seen.last?.url.query())
126 #expect(query.contains("argv=--label&argv=bug"))
127 }
128
129 @Test func thePickerOffersTheRepositorysLabels() async throws {
130 let (client, stub) = try makeClient()
131 stub.enqueue(.init(status: 200, json: """
132 {"protocol_version":1,"data":[{"name":"bug","color":"#d73a4a","issues":2},\
133 {"name":"ios","issues":0}],"exit_code":0}
134 """, match: "argv=label&argv=list"))
135 let model = MRListViewModel(client: client, repoPath: "krz/gitbay")
136
137 await model.loadLabels()
138 await model.loadLabels()
139
140 #expect(model.labels.map(\.name) == ["bug", "ios"])
141 #expect(model.colors.hex("bug") == LabelPalette.hex(name: "bug", stored: "#d73a4a"))
142 #expect(stub.seen.count == 1)
143 }
144}