krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
90da3a7aa2bf991904c1d7201250154ea84f1493
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-03-19T01:07:59Z
Hutch/Views/Tickets/TicketDetailView.swift | 5 +---- Hutch/Views/Tickets/TicketDetailViewModel.swift | 20 ++++++++++++----- HutchTests/TicketDetailViewModelTests.swift | 30 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) @@ -71,10 +71,7 @@ struct TicketDetailView: View { if ticket.status == .resolved { Button { Task { - await viewModel.updateStatus( - status: .reported, - resolution: .unresolved - ) + await viewModel.updateStatus(status: .reported) } } label: { SwiftUI.Label("Reopen", systemImage: "arrow.uturn.backward") @@ -130,6 +130,19 @@ final class TicketDetailViewModel { return lhs.created < rhs.created } + static func statusUpdateInput( + status: TicketStatus, + resolution: TicketResolution? + ) -> [String: any Sendable] { + var input: [String: any Sendable] = [ + "status": status.rawValue + ] + if status == .resolved, let resolution { + input["resolution"] = resolution.rawValue + } + return input + } + init(ownerUsername: String, trackerName: String, trackerId: Int, trackerRid: String, ticketId: Int, client: SRHTClient) { self.ownerUsername = ownerUsername self.trackerName = trackerName @@ -360,16 +373,13 @@ final class TicketDetailViewModel { // MARK: - Ticket Actions - func updateStatus(status: TicketStatus, resolution: TicketResolution) async { + func updateStatus(status: TicketStatus, resolution: TicketResolution? = nil) async { guard !isPerformingAction else { return } isPerformingAction = true error = nil do { - let input: [String: any Sendable] = [ - "status": status.rawValue, - "resolution": resolution.rawValue - ] + let input = Self.statusUpdateInput(status: status, resolution: resolution) _ = try await client.execute( service: .todo, query: Self.updateStatusMutation, new file mode 100644 @@ -0,0 +1,30 @@ +import Foundation +import Testing +@testable import Hutch + +struct TicketDetailViewModelTests { + + @Test + @MainActor + func reopenStatusInputOmitsResolution() { + let input = TicketDetailViewModel.statusUpdateInput( + status: .reported, + resolution: .unresolved + ) + + #expect(input["status"] as? String == TicketStatus.reported.rawValue) + #expect(input["resolution"] == nil) + } + + @Test + @MainActor + func resolveStatusInputIncludesResolution() { + let input = TicketDetailViewModel.statusUpdateInput( + status: .resolved, + resolution: .fixed + ) + + #expect(input["status"] as? String == TicketStatus.resolved.rawValue) + #expect(input["resolution"] as? String == TicketResolution.fixed.rawValue) + } +}