gitbayTests/ListReturnTests.swift
91 lines · 3420 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 emptyPage = #"{"protocol_version":1,"data":{"items":[]},"exit_code":0}"#
16/// A refusal, not a 5xx: the client retries a 5xx once, which would
17/// take the next stub and hide the failure.
18private let refusal = #"{"protocol_version":1,"error":"denied","exit_code":4}"#
19
20private func reads(_ stub: StubProtocol.Box, _ command: String) -> Int {
21 stub.seen.count { $0.url.query()?.contains(command) == true }
22}
23
24/// Returning to a list from a pushed detail screen runs its `.task`
25/// again. `loadIfNeeded()` reads the list only when nothing is shown yet,
26/// so the pages and scroll position the user left are still there.
27@MainActor
28struct ListReturnTests {
29
30 @Test func theIssueListLoadsOnce() async throws {
31 let (client, stub) = try makeClient()
32 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=issue&argv=list"))
33 let model = IssueListViewModel(client: client, repoPath: "krz/gitbay")
34
35 await model.loadIfNeeded()
36 await model.loadIfNeeded()
37
38 #expect(reads(stub, "argv=issue&argv=list") == 1)
39 }
40
41 @Test func theMRListLoadsOnce() async throws {
42 let (client, stub) = try makeClient()
43 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=mr&argv=list"))
44 let model = MRListViewModel(client: client, repoPath: "krz/gitbay")
45
46 await model.loadIfNeeded()
47 await model.loadIfNeeded()
48
49 #expect(reads(stub, "argv=mr&argv=list") == 1)
50 }
51
52 @Test func theBuildListLoadsOnce() async throws {
53 let (client, stub) = try makeClient()
54 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=build&argv=list"))
55 let model = BuildListViewModel(client: client, repoPath: "krz/gitbay")
56
57 await model.loadIfNeeded()
58 await model.loadIfNeeded()
59
60 #expect(reads(stub, "argv=build&argv=list") == 1)
61 }
62
63 @Test func aFailedLoadIsTriedAgainOnReturn() async throws {
64 let (client, stub) = try makeClient()
65 stub.enqueue(.init(status: 403, json: refusal, match: "argv=mr&argv=list"))
66 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=mr&argv=list"))
67 let model = MRListViewModel(client: client, repoPath: "krz/gitbay")
68
69 await model.loadIfNeeded()
70 await model.loadIfNeeded()
71
72 #expect(reads(stub, "argv=mr&argv=list") == 2)
73 guard case .empty = model.state else {
74 Issue.record("expected .empty after the retry, got \(model.state)")
75 return
76 }
77 }
78
79 /// Pull-to-refresh still calls `load()`, which always reads.
80 @Test func loadStillReloads() async throws {
81 let (client, stub) = try makeClient()
82 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=issue&argv=list"))
83 stub.enqueue(.init(status: 200, json: emptyPage, match: "argv=issue&argv=list"))
84 let model = IssueListViewModel(client: client, repoPath: "krz/gitbay")
85
86 await model.loadIfNeeded()
87 await model.load()
88
89 #expect(reads(stub, "argv=issue&argv=list") == 2)
90 }
91}