a native ios client for gitbay

client ios swift

https://gitbay.org

gitbayTests/PagedListTests.swift

ui-smoke
gitbay-ios/gitbayTests/PagedListTests.swift history · blame · raw

125 lines · 4568 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 nonisolated struct Row: Decodable, Sendable, Equatable {
 16    let number: Int
 17}
 18
 19private func page(_ numbers: [Int], next: String? = nil) -> String {
 20    let items = numbers.map { #"{"number":\#($0)}"# }.joined(separator: ",")
 21    let nextField = next.map { #","next":"\#($0)""# } ?? ""
 22    return #"{"protocol_version":1,"data":{"items":[\#(items)]\#(nextField)},"exit_code":0}"#
 23}
 24
 25struct ClientPagingTests {
 26
 27    @Test func readPageSendsLimitAndCursorAndDecodesNext() async throws {
 28        let (client, stub) = try makeClient()
 29        stub.enqueue(.init(status: 200, json: page([1, 2], next: "abc")))
 30        stub.enqueue(.init(status: 200, json: page([3])))
 31
 32        let first = try await client.readPage(["issue", "list", "krz/gitbay"], of: Row.self, limit: 2)
 33        let second = try await client.readPage(
 34            ["issue", "list", "krz/gitbay"], of: Row.self, limit: 2, cursor: first.next
 35        )
 36
 37        #expect(first.items == [Row(number: 1), Row(number: 2)])
 38        #expect(first.next == "abc")
 39        #expect(second.items == [Row(number: 3)])
 40        #expect(second.next == nil)
 41        #expect(stub.seen[0].url.query() ==
 42            "argv=issue&argv=list&argv=krz/gitbay&argv=--limit&argv=2")
 43        #expect(stub.seen[1].url.query() ==
 44            "argv=issue&argv=list&argv=krz/gitbay&argv=--limit&argv=2&argv=--cursor&argv=abc")
 45    }
 46}
 47
 48@MainActor
 49struct PagedListModelTests {
 50
 51    private func makeList(pageSize: Int = 2) throws -> (PagedListModel<Row>, StubProtocol.Box) {
 52        let (client, stub) = try makeClient()
 53        let list = PagedListModel<Row>(
 54            client: client,
 55            argv: ["issue", "list", "krz/gitbay"],
 56            emptyMessage: "nothing",
 57            pageSize: pageSize
 58        )
 59        return (list, stub)
 60    }
 61
 62    @Test func loadMoreAppendsUntilTheCursorRunsOut() async throws {
 63        let (list, stub) = try makeList()
 64        stub.enqueue(.init(status: 200, json: page([1, 2], next: "c1")))
 65        stub.enqueue(.init(status: 200, json: page([3, 4], next: "c2")))
 66        stub.enqueue(.init(status: 200, json: page([5])))
 67
 68        await list.reload()
 69        #expect(list.state.value == [Row(number: 1), Row(number: 2)])
 70        #expect(list.hasMore)
 71
 72        await list.loadMore()
 73        await list.loadMore()
 74        #expect(list.state.value?.map(\.number) == [1, 2, 3, 4, 5])
 75        #expect(!list.hasMore)
 76
 77        // Further calls are no-ops, not requests.
 78        await list.loadMore()
 79        #expect(stub.seen.count == 3)
 80        #expect(stub.seen[1].url.query()?.contains("argv=--cursor&argv=c1") == true)
 81        #expect(stub.seen[2].url.query()?.contains("argv=--cursor&argv=c2") == true)
 82    }
 83
 84    @Test func anEmptyFirstPageIsAnEmptyState() async throws {
 85        let (list, stub) = try makeList()
 86        stub.enqueue(.init(status: 200, json: page([])))
 87
 88        await list.reload()
 89
 90        guard case .empty(let message) = list.state else {
 91            Issue.record("expected .empty, got \(list.state)")
 92            return
 93        }
 94        #expect(message == "nothing")
 95    }
 96
 97    @Test func aFailedLaterPageKeepsTheListAndTheCursor() async throws {
 98        let (list, stub) = try makeList()
 99        stub.enqueue(.init(status: 200, json: page([1, 2], next: "c1")))
100        stub.enqueue(.init(status: 500, json: #"{"protocol_version":1,"error":"boom","exit_code":1}"#))
101        stub.enqueue(.init(status: 500, json: #"{"protocol_version":1,"error":"boom","exit_code":1}"#))
102
103        await list.reload()
104        await list.loadMore()
105
106        #expect(list.state.value == [Row(number: 1), Row(number: 2)])
107        #expect(list.hasMore) // the cursor survives for a retry
108
109        stub.enqueue(.init(status: 200, json: page([3])))
110        await list.loadMore()
111        #expect(list.state.value?.map(\.number) == [1, 2, 3])
112    }
113
114    @Test func reloadDropsTheOldCursorAndItems() async throws {
115        let (list, stub) = try makeList()
116        stub.enqueue(.init(status: 200, json: page([1, 2], next: "c1")))
117        stub.enqueue(.init(status: 200, json: page([9])))
118
119        await list.reload()
120        await list.reload()
121
122        #expect(list.state.value == [Row(number: 9)])
123        #expect(!list.hasMore)
124    }
125}