gitbayTests/DashboardViewModelTests.swift
92 lines · 3735 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 let dashboardJSON = """
16 {"protocol_version":1,"data":{\
17 "review_queue":[{"repo":"krz/gitbay-ios","number":3,"title":"adopt aggregate","author":"cmc",\
18 "state":"open","updated_at":"2026-08-27T10:00:00.000Z"}],\
19 "pinned":[{"path":"krz/gitbay","visibility":"public","description":"a CLI-first git forge"}],\
20 "open_mrs":[{"repo":"krz/gitbay-ios","number":3,"title":"adopt aggregate","author":"cmc",\
21 "state":"open","updated_at":"2026-08-27T10:00:00.000Z"}],\
22 "assigned_issues":[{"repo":"krz/gitbay","number":11,"title":"iOS app","author":"krz",\
23 "state":"open","updated_at":"2026-08-26T10:00:00.000Z"}],\
24 "open_issues":[{"repo":"krz/gitbay","number":11,"title":"iOS app","author":"krz",\
25 "state":"open","updated_at":"2026-08-26T10:00:00.000Z"}],\
26 "recent_activity":[{"id":7,"repo":"krz/gitbay-ios","actor":"cmc","kind":"mr.merged",\
27 "data":{"number":2},"created_at":"2026-08-27T10:30:00.000Z"}],\
28 "builds":[{"repo":"krz/gitbay","number":9,"job":"ci","status":"success",\
29 "sha":"65ba14e0000000000000","ref":"refs/heads/main",\
30 "created_at":"2026-08-27T09:00:00.000Z","finished_at":"2026-08-27T09:05:00.000Z"}]},\
31 "exit_code":0}
32 """
33
34private let emptyDashboardJSON = """
35 {"protocol_version":1,"data":{"pinned":[],"open_mrs":[],"assigned_issues":[],"builds":[]},\
36 "exit_code":0}
37 """
38
39@MainActor
40struct DashboardViewModelTests {
41
42 @Test func oneReadFillsEverySection() async throws {
43 let (client, stub) = try makeClient()
44 stub.enqueue(.init(status: 200, json: dashboardJSON))
45 let model = DashboardViewModel(client: client)
46
47 await model.load()
48
49 let data = try #require(model.state.value)
50 #expect(data.pinned.map(\.path) == ["krz/gitbay"])
51 #expect(data.reviewQueue.map(\.id) == ["krz/gitbay-ios#3"])
52 #expect(data.openMRs.map(\.id) == ["krz/gitbay-ios#3"])
53 #expect(data.assignedIssues.first?.number == 11)
54 #expect(data.openIssues.first?.number == 11)
55 #expect(data.recentActivity.first?.phrase == "merged !2")
56 #expect(data.builds.first?.status == "success")
57 // The whole screen cost exactly one request.
58 #expect(stub.seen.count == 1)
59 #expect(stub.seen.first?.url.query() == "argv=dashboard")
60 }
61
62 @Test func emptyAggregateStillLoads() async throws {
63 let (client, stub) = try makeClient()
64 stub.enqueue(.init(status: 200, json: emptyDashboardJSON))
65 let model = DashboardViewModel(client: client)
66
67 await model.load()
68
69 let data = try #require(model.state.value)
70 #expect(data.pinned.isEmpty)
71 #expect(data.openMRs.isEmpty)
72 #expect(data.reviewQueue.isEmpty)
73 #expect(data.openIssues.isEmpty)
74 #expect(data.recentActivity.isEmpty)
75 }
76
77 @Test func aFailureIsTheScreensState() async throws {
78 let (client, stub) = try makeClient()
79 stub.enqueue(.init(status: 500, json:
80 #"{"protocol_version":1,"error":"boom","exit_code":1}"#))
81 stub.enqueue(.init(status: 500, json:
82 #"{"protocol_version":1,"error":"boom","exit_code":1}"#))
83 let model = DashboardViewModel(client: client)
84
85 await model.load()
86
87 guard case .failed = model.state else {
88 Issue.record("expected .failed, got \(model.state)")
89 return
90 }
91 }
92}