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 <noreply@anthropic.com>
This commit is contained in:
systemblue 2026-06-11 13:56:23 -04:00
parent f8a050e484
commit f39e9f682d
3 changed files with 57 additions and 8 deletions

View file

@ -9,6 +9,7 @@ final class PaginationState<Item> {
private(set) var hasLoaded = false
var errorMessage: String?
var showError = false
var notFound = false
private var currentPage = 1
private var loadTask: Task<Void, Never>?
@ -44,6 +45,7 @@ final class PaginationState<Item> {
hasLoaded = false
isLoading = true
showError = false
notFound = false
let pageSize = pageSize
let task = Task {
do {

View file

@ -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<Void, Never> {
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 []
}
}
}

View file

@ -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<String>(pageSize: 5)
#expect(!pagination.notFound)
}
@Test @MainActor func notFoundSetByFetchClosureShowsNoAlert() async {
let pagination = PaginationState<String>(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<String>(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"]
}