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

Omit resolution when reopening resolved tickets
 Hutch/Views/Tickets/TicketDetailView.swift      |  5 +----
 Hutch/Views/Tickets/TicketDetailViewModel.swift | 20 ++++++++++++-----
 HutchTests/TicketDetailViewModelTests.swift     | 30 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+), 9 deletions(-)

diff --git a/Hutch/Views/Tickets/TicketDetailView.swift b/Hutch/Views/Tickets/TicketDetailView.swift
index 6b2be4f..f553bd8 100644
--- a/Hutch/Views/Tickets/TicketDetailView.swift
+++ b/Hutch/Views/Tickets/TicketDetailView.swift
@@ -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")
diff --git a/Hutch/Views/Tickets/TicketDetailViewModel.swift b/Hutch/Views/Tickets/TicketDetailViewModel.swift
index 70eaf0a..90e33aa 100644
--- a/Hutch/Views/Tickets/TicketDetailViewModel.swift
+++ b/Hutch/Views/Tickets/TicketDetailViewModel.swift
@@ -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,
diff --git a/HutchTests/TicketDetailViewModelTests.swift b/HutchTests/TicketDetailViewModelTests.swift
new file mode 100644
index 0000000..bf0bc60
--- /dev/null
+++ b/HutchTests/TicketDetailViewModelTests.swift
@@ -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)
+    }
+}