From f39e9f682d99ec74f49dfb4d0c518c83d87485af Mon Sep 17 00:00:00 2001 From: systemblue Date: Thu, 11 Jun 2026 13:56:23 -0400 Subject: [PATCH] fix: show graceful empty state when repository has issues turned off When the Forgejo API returns 404 for the issues endpoint (issues disabled for the repository), display a ContentUnavailableView instead of surfacing the raw HTTP error alert. Co-Authored-By: Claude Sonnet 4.6 --- Forji/Forji/Helpers/PaginationState.swift | 2 ++ Forji/Forji/Views/IssueListView.swift | 25 ++++++++++---- Forji/ForjiTests/PaginationStateTests.swift | 38 ++++++++++++++++++++- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Forji/Forji/Helpers/PaginationState.swift b/Forji/Forji/Helpers/PaginationState.swift index af74840..2723525 100644 --- a/Forji/Forji/Helpers/PaginationState.swift +++ b/Forji/Forji/Helpers/PaginationState.swift @@ -9,6 +9,7 @@ final class PaginationState { private(set) var hasLoaded = false var errorMessage: String? var showError = false + var notFound = false private var currentPage = 1 private var loadTask: Task? @@ -44,6 +45,7 @@ final class PaginationState { hasLoaded = false isLoading = true showError = false + notFound = false let pageSize = pageSize let task = Task { do { diff --git a/Forji/Forji/Views/IssueListView.swift b/Forji/Forji/Views/IssueListView.swift index fe3763c..75de1a5 100644 --- a/Forji/Forji/Views/IssueListView.swift +++ b/Forji/Forji/Views/IssueListView.swift @@ -38,6 +38,12 @@ struct IssueListView: View { List { if pagination.isLoading, pagination.items.isEmpty { LoadingListSection() + } else if pagination.notFound { + ContentUnavailableView { + Label("Issues Unavailable", systemImage: "exclamationmark.circle") + } description: { + Text("Issues are not available for this repository.") + } } else if pagination.items.isEmpty { ContentUnavailableView { Label( @@ -116,13 +122,18 @@ struct IssueListView: View { private func reloadIssues(clearItems: Bool = false) -> Task { guard let issueService else { return Task {} } return pagination.reload(clearItems: clearItems) { [self] page, limit in - try await issueService.fetchIssues( - owner: owner, - repo: repo, - state: stateFilter.rawValue, - page: page, - limit: limit, - ) + do { + return try await issueService.fetchIssues( + owner: owner, + repo: repo, + state: stateFilter.rawValue, + page: page, + limit: limit, + ) + } catch let error as ServiceError where error.httpStatusCode == 404 { + pagination.notFound = true + return [] + } } } diff --git a/Forji/ForjiTests/PaginationStateTests.swift b/Forji/ForjiTests/PaginationStateTests.swift index 32cd25f..d1dcd45 100644 --- a/Forji/ForjiTests/PaginationStateTests.swift +++ b/Forji/ForjiTests/PaginationStateTests.swift @@ -1,3 +1,4 @@ +// swiftlint:disable file_length import Foundation import Testing @testable import Forji @@ -359,6 +360,41 @@ struct PaginationStateHasLoadedTests { } } +// MARK: - notFound + +struct PaginationStateNotFoundTests { + + @Test @MainActor func notFoundFalseInitially() { + let pagination = PaginationState(pageSize: 5) + #expect(!pagination.notFound) + } + + @Test @MainActor func notFoundSetByFetchClosureShowsNoAlert() async { + let pagination = PaginationState(pageSize: 20) + await pagination.reload { _, _ in + pagination.notFound = true + return [] + }.value + #expect(pagination.notFound) + #expect(pagination.items.isEmpty) + #expect(!pagination.showError) + #expect(pagination.hasLoaded) + } + + @Test @MainActor func reloadClearsNotFound() async { + let pagination = PaginationState(pageSize: 20) + await pagination.reload { _, _ in + pagination.notFound = true + return [] + }.value + #expect(pagination.notFound) + + await pagination.reload { _, _ in ["ok"] }.value + #expect(!pagination.notFound) + #expect(pagination.items == ["ok"]) + } +} + // MARK: - loadMore struct PaginationStateLoadMoreTests { @@ -368,7 +404,7 @@ struct PaginationStateLoadMoreTests { await pagination.reload { _, _ in ["a", "b"] }.value #expect(pagination.hasMore) - await pagination.loadMore { page, limit in + await pagination.loadMore { page, _ in #expect(page == 2) return ["c", "d"] }