gitbayTests/MRLabelTests.swift

v1.13.0
gitbay-ios/gitbayTests/MRLabelTests.swift history · blame · raw

144 lines · 5546 bytes

  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}