chore: formatting and lints

This commit is contained in:
Stefan Hausotte 2026-06-15 12:19:00 +02:00
parent a7491daffc
commit 69f7923a52
34 changed files with 99 additions and 86 deletions

View file

@ -1,2 +1,3 @@
--swiftversion 6.2
--disable redundantMemberwiseInit
--disable swiftTestingTestCaseNames

View file

@ -230,7 +230,7 @@
mainGroup = DEC49F182F3CE05200E7DD54;
minimizedProjectReferenceProxies = 1;
packageReferences = (
DE00000000000005000000AA /* XCLocalSwiftPackageReference "../../ForgejoKit" */,
DE00000000000005000000AA /* XCRemoteSwiftPackageReference "ForgejoKit" */,
DEC49F6B2F3D00C700E7DD54 /* XCRemoteSwiftPackageReference "textual" */,
DEC49F812F3D173F00E7DD54 /* XCRemoteSwiftPackageReference "HighlightSwift" */,
);
@ -633,14 +633,15 @@
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
DE00000000000005000000AA /* XCLocalSwiftPackageReference "../../ForgejoKit" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../ForgejoKit;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCRemoteSwiftPackageReference section */
DE00000000000005000000AA /* XCRemoteSwiftPackageReference "ForgejoKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://codeberg.org/secana/ForgejoKit.git";
requirement = {
kind = exactVersion;
version = 0.8.0;
};
};
DEC49F6B2F3D00C700E7DD54 /* XCRemoteSwiftPackageReference "textual" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/gonzalezreal/textual";
@ -662,12 +663,12 @@
/* Begin XCSwiftPackageProductDependency section */
DE00000000000002000000AA /* ForgejoKit */ = {
isa = XCSwiftPackageProductDependency;
package = DE00000000000005000000AA /* XCLocalSwiftPackageReference "../../ForgejoKit" */;
package = DE00000000000005000000AA /* XCRemoteSwiftPackageReference "ForgejoKit" */;
productName = ForgejoKit;
};
DE00000000000004000000AA /* ForgejoKit */ = {
isa = XCSwiftPackageProductDependency;
package = DE00000000000005000000AA /* XCLocalSwiftPackageReference "../../ForgejoKit" */;
package = DE00000000000005000000AA /* XCRemoteSwiftPackageReference "ForgejoKit" */;
productName = ForgejoKit;
};
DEC49F6D2F3D023400E7DD54 /* Textual */ = {

View file

@ -1,6 +1,15 @@
{
"originHash" : "931ec0beeaf4e6a5eaa0afab6f815f97bc126bda7a2c9f001c10de58585e766f",
"pins" : [
{
"identity" : "forgejokit",
"kind" : "remoteSourceControl",
"location" : "https://codeberg.org/secana/ForgejoKit.git",
"state" : {
"revision" : "485c147e7f9762c06f51029de47464fb41abc4af",
"version" : "0.8.0"
}
},
{
"identity" : "highlightswift",
"kind" : "remoteSourceControl",

View file

@ -150,7 +150,7 @@ struct ContentView: View {
defaultInstance.lastUsed = Date()
try? modelContext.save()
} catch {
// Auto-login failed fall through to instance list
// Auto-login failed, fall through to instance list
}
}

View file

@ -152,7 +152,7 @@ enum HTMLParser {
}
}
// 2. Inline HTML check paragraphs for inline tags
// 2. Inline HTML, check paragraphs for inline tags
if let inlinePattern = inlineHTMLPattern {
let paragraphs = splitIntoParagraphs(masked)
for para in paragraphs {

View file

@ -36,7 +36,7 @@ struct WorkflowStatusStyle {
let label: String
/// Maps Forgejo's single status value to a UI style. Used for runs, jobs,
/// and steps they share the same status vocabulary. Forgejo's known
/// and steps, they share the same status vocabulary. Forgejo's known
/// statuses: success, failure, cancelled, skipped, running, waiting,
/// blocked, unknown. Anything else falls back to a question-mark glyph.
static func forStatus(_ status: String) -> WorkflowStatusStyle {
@ -122,7 +122,7 @@ extension WorkflowRun {
}
/// Elapsed time as TimeInterval. Forgejo serialises a Go `time.Duration`
/// int64 nanoseconds so divide by 1_000_000_000. Falls back to the
/// , int64 nanoseconds, so divide by 1_000_000_000. Falls back to the
/// timestamps only when both are sane (post-2000).
var duration: TimeInterval? {
if let nanos = durationNanos, nanos > 0 {

View file

@ -116,7 +116,7 @@ class AuthenticationService {
username: instance.username,
)
} catch {
// Keychain delete failed log in debug builds
// Keychain delete failed, log in debug builds
#if DEBUG
print("Keychain delete failed during logout: \(error)")
#endif
@ -171,7 +171,7 @@ class AuthenticationService {
isAuthenticated = true
return
} catch {
// Token is invalid/expired only fall through to password if this is not a token-only instance
// Token is invalid/expired, only fall through to password if this is not a token-only instance
if useTokenAuth {
throw SessionRestoreError.fromTokenValidationError(error)
}

View file

@ -2,9 +2,9 @@ import ForgejoKit
import SwiftUI
/// Markdown to insert when an attachment is uploaded. Images use embed syntax
/// (`![name](url)`) so they render inline; everything else (logs, PDFs, archives,
/// ) uses link syntax (`[name](url)`) so it appears as a clickable link rather
/// than a broken inline image.
/// (`![name](url)`) so they render inline; every other file type (logs, PDFs,
/// archives, and so on) uses link syntax (`[name](url)`) so it appears as a
/// clickable link rather than a broken inline image.
func attachmentMarkdown(for attachment: Attachment) -> String {
if attachment.isImage {
"![\(attachment.name)](\(attachment.browserDownloadUrl))"

View file

@ -146,7 +146,7 @@ struct CommitHistoryView: View {
repo: repo,
)
} catch {
// Non-critical branch selector stays disabled
// Non-critical, branch selector stays disabled
}
}

View file

@ -100,7 +100,7 @@ struct HomeView: View {
do {
unreadCount = try await notificationService.fetchUnreadCount()
} catch {
// Silently ignore badge is non-critical, keep the prior count
// Silently ignore, badge is non-critical, keep the prior count
return
}
} else {

View file

@ -55,7 +55,7 @@ struct IssueListView: View {
} description: {
Text(
stateFilter == .open
? "All clear no open issues to review."
? "All clear, no open issues to review."
: "No closed issues found.",
)
}

View file

@ -106,7 +106,7 @@ struct MarkdownToolbar: View {
private func uploadTestImage() async {
guard let onUploadImage else { return }
// 1×1 transparent PNG used in UI tests to skip the Photos picker.
// 1×1 transparent PNG, used in UI tests to skip the Photos picker.
let base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
guard let pngData = Data(base64Encoded: base64) else { return }
await runUpload { try await onUploadImage(pngData, "test.png", "image/png") }
@ -114,7 +114,7 @@ struct MarkdownToolbar: View {
private func uploadTestFile() async {
guard let onUploadImage else { return }
// Plain-text log file used in UI tests to exercise the non-image
// Plain-text log file, used in UI tests to exercise the non-image
// attachment path (link markdown + file-row display).
let logData = Data("line1 ERROR boom\nline2 WARN stuff\nline3 done\n".utf8)
await runUpload { try await onUploadImage(logData, "server.log", "text/plain") }

View file

@ -55,7 +55,7 @@ struct PullRequestListView: View {
} description: {
Text(
stateFilter == .open
? "All clear no open pull requests."
? "All clear, no open pull requests."
: "No closed pull requests found.",
)
}

View file

@ -185,7 +185,7 @@ struct RepositoryDetailView: View {
repo: repository.repoName,
)
} catch {
// Non-critical branch selector stays disabled
// Non-critical, branch selector stays disabled
}
}
.toolbar {
@ -504,7 +504,7 @@ struct RepositoryCodeView: View {
)
readmeContent = fileContent.decodedContent
} catch {
// README exists in listing but couldn't be fetched ignore
// README exists in listing but couldn't be fetched, ignore
}
}

View file

@ -69,7 +69,7 @@ struct SearchableOverviewView<Row: View, Detail: View, CreateView: View>: View {
return "No \(itemNoun) matching your search."
}
if stateFilter == .open {
return "All clear no open \(itemNoun) to review."
return "All clear, no open \(itemNoun) to review."
}
let prefix = stateFilter == .all ? "" : stateFilter.rawValue + " "
return "No \(prefix)\(itemNoun) found."
@ -85,7 +85,7 @@ struct SearchableOverviewView<Row: View, Detail: View, CreateView: View>: View {
case .closed: "Closed"
case .all: "All"
}
// Scope is ignored during search only show state
// Scope is ignored during search, only show state
if !searchText.isEmpty || (stateFilter == .open && involvementScope == .involved) {
return stateLabel
}

View file

@ -72,7 +72,7 @@ struct WorkflowJobView: View {
Label("No Steps Recorded", systemImage: "tray")
.foregroundStyle(.secondary)
} description: {
Text("This job has no recorded steps it likely never started.")
Text("This job has no recorded steps, it likely never started.")
}
} header: {
experimentalHeader

View file

@ -196,7 +196,7 @@ struct WorkflowRunDetailView: View {
isLoading = false
// Best-effort experimental fetch don't surface errors if it fails
// Best-effort experimental fetch, don't surface errors if it fails
// (e.g. older Forgejo versions without this route, or auth quirks).
if let runIndex = run?.indexInRepo {
await loadRunView(runIndex: runIndex)
@ -214,7 +214,7 @@ struct WorkflowRunDetailView: View {
logCursors: [],
)
} catch {
// Silent experimental endpoint failure shouldn't block run detail.
// Silent, experimental endpoint failure shouldn't block run detail.
}
}
@ -222,7 +222,7 @@ struct WorkflowRunDetailView: View {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .abbreviated
return formatter.string(from: seconds) ?? ""
return formatter.string(from: seconds) ?? "-"
}
}

View file

@ -127,7 +127,7 @@ struct KeychainManagerTests {
_ = try await KeychainManager.shared.getPassword(for: raw, username: "user")
Issue.record("Should not find password with non-normalized key")
} catch is KeychainError {
// Expected keys differ
// Expected, keys differ
}
// Clean up
@ -156,7 +156,7 @@ struct KeychainManagerTests {
_ = try await KeychainManager.shared.getToken(for: server, username: username)
Issue.record("Expected KeychainError.notFound for token after deleteCredentials")
} catch is KeychainError {
// Expected this is the assertion that fails under the old leak
// Expected, this is the assertion that fails under the old leak
} catch {
Issue.record("Unexpected error: \(error)")
}

View file

@ -44,7 +44,7 @@ struct MarkdownComponentsTests {
}
@Test func repoRelativePathReturnsNilForBranchOnly() {
// URL ends right after the ref no file path follows
// URL ends right after the ref, no file path follows
let url = URL(string: "https://forgejo.example.com/owner/repo/src/branch/main")!
#expect(repoRelativePath(from: url) == nil)
}

View file

@ -71,12 +71,12 @@ struct NotificationPollingIntegrationTests {
let ids = Set(notifications.map(\.id))
#expect(!ids.isEmpty)
// Seed should record all current IDs
// Seed, should record all current IDs
store.seed(ids: ids, for: normalizedURL, username: Self.username)
#expect(store.isSeeded(for: normalizedURL, username: Self.username))
#expect(store.seenIDs(for: normalizedURL, username: Self.username) == ids)
// Simulate a poll returning the same IDs no new notifications
// Simulate a poll returning the same IDs, no new notifications
let newIDs = ids.subtracting(store.seenIDs(for: normalizedURL, username: Self.username))
#expect(newIDs.isEmpty, "After seeding with current IDs, diff should be empty")
}
@ -93,11 +93,11 @@ struct NotificationPollingIntegrationTests {
let allIDs = Set(notifications.map(\.id))
#expect(allIDs.count >= 2, "Need at least 2 notifications for this test")
// Seed with only a subset simulates having seen all but one
// Seed with only a subset, simulates having seen all but one
let partial = Set(allIDs.dropFirst())
store.seed(ids: partial, for: normalizedURL, username: Self.username)
// Now diff the first ID should show as "new"
// Now diff, the first ID should show as "new"
let seenIDs = store.seenIDs(for: normalizedURL, username: Self.username)
let newIDs = allIDs.subtracting(seenIDs)
#expect(newIDs.count == 1, "Expected exactly 1 new notification after partial seed")
@ -123,7 +123,7 @@ struct NotificationPollingIntegrationTests {
store.seed(ids: seeded, for: normalizedURL, username: Self.username)
#expect(store.seenIDs(for: normalizedURL, username: Self.username).contains(999_999))
// Prune only keep IDs that the server still reports as unread
// Prune, only keep IDs that the server still reports as unread
store.prune(keeping: serverIDs, for: normalizedURL, username: Self.username)
#expect(!store.seenIDs(for: normalizedURL, username: Self.username).contains(999_999))
#expect(store.seenIDs(for: normalizedURL, username: Self.username) == serverIDs)
@ -150,7 +150,7 @@ struct NotificationPollingIntegrationTests {
let store = SeenNotificationStore(suiteName: "de.hausotte.Forji.test.\(UUID().uuidString)")
#expect(!store.isSeeded(for: normalizedURL, username: Self.username))
// Seed the store first poll seeds without posting notifications
// Seed the store, first poll seeds without posting notifications
let firstPollIDs = Set(notifications.map(\.id))
store.seed(ids: firstPollIDs, for: normalizedURL, username: Self.username)
@ -191,7 +191,7 @@ struct NotificationPollingIntegrationTests {
// Save with current (post-migration) accessibility
try await KeychainManager.shared.saveToken(token, for: server, username: user)
// Run migration should read + delete + re-save without losing data
// Run migration, should read + delete + re-save without losing data
await KeychainManager.shared.migrateAccessibility(for: server, username: user)
// Verify token is still accessible
@ -283,7 +283,7 @@ struct NotificationPollingIntegrationTests {
let service = NotificationService(client: client)
let store = SeenNotificationStore(suiteName: "de.hausotte.Forji.test.\(UUID().uuidString)")
// Initial poll seed seen store
// Initial poll, seed seen store
let initial = try await service.fetchNotifications(
statusTypes: ["unread"], page: 1, limit: 50
)
@ -295,14 +295,14 @@ struct NotificationPollingIntegrationTests {
let markedID = initial[0].id
try await service.markAsRead(id: markedID)
// Next poll fewer unreads
// Next poll, fewer unreads
let afterMark = try await service.fetchNotifications(
statusTypes: ["unread"], page: 1, limit: 50
)
let afterIDs = Set(afterMark.map(\.id))
#expect(!afterIDs.contains(markedID), "Marked notification should no longer be unread")
// Prune the marked ID should be removed from seen store
// Prune, the marked ID should be removed from seen store
store.prune(keeping: afterIDs, for: normalizedURL, username: Self.username)
#expect(
!store.seenIDs(for: normalizedURL, username: Self.username).contains(markedID),
@ -319,7 +319,7 @@ struct NotificationPollingIntegrationTests {
let server = "https://test.example.com"
let user = "testuser"
// Seed with partial IDs ID 1 is "seen", ID 2 is "new"
// Seed with partial IDs, ID 1 is "seen", ID 2 is "new"
store.seed(ids: [1], for: server, username: user)
// Simulate a poll returning IDs [1, 2]
@ -460,12 +460,12 @@ struct NotificationPollingIntegrationTests {
let allIDs = Set(notifications.map(\.id))
#expect(allIDs.count >= 2, "Need at least 2 notifications")
// Seed with a subset simulate having seen all but one
// Seed with a subset, simulate having seen all but one
let store = SeenNotificationStore(suiteName: "de.hausotte.Forji.test.\(UUID().uuidString)")
let partial = Set(allIDs.dropFirst())
store.seed(ids: partial, for: normalizedURL, username: Self.username)
// Diff should detect the missing ID as new
// Diff, should detect the missing ID as new
let seenIDs = store.seenIDs(for: normalizedURL, username: Self.username)
let newIDs = allIDs.subtracting(seenIDs)
#expect(newIDs.count == 1, "Expected exactly 1 new notification")

View file

@ -147,13 +147,13 @@ struct PaginationStateConcurrentTests {
}
await yieldUntil { fetcherA.isPending }
// Start reload B cancels A's internal task
// Start reload B, cancels A's internal task
let taskB = pagination.reload { page, limit in
try await fetcherB.fetch(page: page, limit: limit)
}
await yieldUntil { fetcherB.isPending }
// Complete both only B's results should be applied
// Complete both, only B's results should be applied
fetcherA.complete(returning: ["stale"])
fetcherB.complete(returning: ["fresh"])
await taskB.value
@ -177,7 +177,7 @@ struct PaginationStateConcurrentTests {
}
await yieldUntil { fetcherB.isPending }
// Complete A first (stale, its task was cancelled) must be discarded
// Complete A first (stale, its task was cancelled), must be discarded
fetcherA.complete(returning: ["stale"])
await yieldUntil { fetcherA.callCount == 1 }
@ -213,7 +213,7 @@ struct PaginationStateConcurrentTests {
}
await yieldUntil { fetcherC.isPending }
// Complete in order A, B, C only C should be applied
// Complete in order A, B, C, only C should be applied
fetcherA.complete(returning: ["a"])
fetcherB.complete(returning: ["b"])
fetcherC.complete(returning: ["c"])
@ -238,7 +238,7 @@ struct PaginationStateConcurrentTests {
}
await yieldUntil { fetcherB.isPending }
// A fails with error but its task was cancelled, so no alert
// A fails with error, but its task was cancelled, so no alert
fetcherA.complete(throwing: URLError(.badServerResponse))
fetcherB.complete(returning: ["ok"])
await taskB.value
@ -264,7 +264,7 @@ struct PaginationStateConcurrentTests {
await yieldUntil { fetcherB.isPending }
#expect(pagination.isLoading)
// Complete stale A isLoading must REMAIN true (B is still in flight)
// Complete stale A, isLoading must REMAIN true (B is still in flight)
fetcherA.complete(returning: ["stale"])
await yieldUntil { fetcherA.callCount == 1 }
#expect(pagination.isLoading, "isLoading must stay true while latest reload (B) is pending")
@ -287,7 +287,7 @@ struct PaginationStateConcurrentTests {
}
await yieldUntil { initialFetcher.isPending }
// Simulate onChange: user changed filter just call reload again
// Simulate onChange: user changed filter, just call reload again
let filterTask = pagination.reload(clearItems: true) { page, limit in
try await filterFetcher.fetch(page: page, limit: limit)
}
@ -463,11 +463,11 @@ struct PaginationStateLoadMoreTests {
}
await yieldUntil { freshFetcher.isPending }
// loadMore completes should be discarded (task was cancelled)
// loadMore completes, should be discarded (task was cancelled)
moreFetcher.complete(returning: ["stale-more"])
await moreTask.value
// reload completes should be applied
// reload completes, should be applied
freshFetcher.complete(returning: ["fresh"])
await reloadTask.value
@ -485,7 +485,7 @@ struct PaginationStateDedupeTests {
pagination.dedupeKey = { $0 }
await pagination.reload { _, _ in ["a", "b"] }.value
// "b" resurfaces on page 2 only "c" is new
// "b" resurfaces on page 2, only "c" is new
await pagination.loadMore { _, _ in ["b", "c"] }
#expect(pagination.items == ["a", "b", "c"])
#expect(pagination.hasMore) // the page contributed a new item

View file

@ -25,7 +25,7 @@ final class AttachmentUITests: ForgejoUITestBase {
XCTAssertTrue(commentButton.waitForExistence(timeout: 5))
commentButton.tap()
// Tap the image upload button twice in test mode each tap uploads a
// Tap the image upload button twice, in test mode each tap uploads a
// hardcoded PNG instead of presenting the Photos picker. Two taps produce
// two distinct attachments and two markdown references.
let imageButton = app.buttons["markdown-toolbar-image"]
@ -33,13 +33,13 @@ final class AttachmentUITests: ForgejoUITestBase {
let textEditor = app.textViews["markdown-text-editor"].firstMatch
XCTAssertTrue(textEditor.waitForExistence(timeout: 5))
// First upload wait until one markdown reference is inserted before tapping
// First upload, wait until one markdown reference is inserted before tapping
// again, so the second upload appends rather than racing the first.
imageButton.tap()
expectation(for: NSPredicate(format: "value MATCHES '.*!\\\\[.*'"), evaluatedWith: textEditor)
waitForExpectations(timeout: 15, handler: nil)
// Second upload wait until two markdown references are present.
// Second upload, wait until two markdown references are present.
imageButton.tap()
let twoRefs = NSPredicate(format: "value MATCHES '(.*!\\\\[.*){2,}'")
expectation(for: twoRefs, evaluatedWith: textEditor)
@ -120,7 +120,7 @@ final class AttachmentUITests: ForgejoUITestBase {
XCTAssertTrue(commentButton.waitForExistence(timeout: 5))
commentButton.tap()
// Tap the file upload button in test mode this uploads a hardcoded server.log.
// Tap the file upload button, in test mode this uploads a hardcoded server.log.
let fileButton = app.buttons["markdown-toolbar-file"]
XCTAssertTrue(fileButton.waitForExistence(timeout: 5))
let textEditor = app.textViews["markdown-text-editor"].firstMatch
@ -132,7 +132,7 @@ final class AttachmentUITests: ForgejoUITestBase {
waitForExpectations(timeout: 15, handler: nil)
// A non-image attachment must use link syntax [name](url), NOT image-embed
// syntax ![name](url) otherwise it would render as a broken inline image.
// syntax ![name](url), otherwise it would render as a broken inline image.
let editorValue = textEditor.value as? String ?? ""
XCTAssertTrue(
editorValue.contains("[server.log]"),

View file

@ -14,7 +14,7 @@ class ForgejoReadOnlyUITestBase: XCTestCase, UITestNavigating {
guard let url = ForgejoUITestBase.resolveTestServerURL(),
!url.isEmpty
else {
// Can't XCTSkip from class setUp individual tests will skip
// Can't XCTSkip from class setUp, individual tests will skip
return
}
sharedServerURL = url
@ -60,7 +60,7 @@ class ForgejoReadOnlyUITestBase: XCTestCase, UITestNavigating {
// Ensure we're at the home screen (Repositories tab as anchor)
let reposTab = app.tabBars.buttons["Repositories"]
if !reposTab.waitForExistence(timeout: 5) {
// Recovery: app may have crashed or navigated away relaunch
// Recovery: app may have crashed or navigated away, relaunch
let freshApp = XCUIApplication()
freshApp.launchArguments += [
"-dev_serverURL", Self.sharedServerURL,

View file

@ -9,7 +9,7 @@ final class ForjiUITestsLaunchTests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
// Skip during integration test runs these tests need manual credentials
// Skip during integration test runs, these tests need manual credentials
if FileManager.default.fileExists(atPath: "/tmp/forgejo_test_url.txt") {
throw XCTSkip("Skipping launch tests during integration test run")
}

View file

@ -26,7 +26,7 @@ final class IssueMutatingUITests: ForgejoUITestBase {
openButton.tap()
XCTAssertTrue(app.staticTexts["Test issue 1 from integration tests"].waitForExistence(timeout: 10))
// Issue detail title, comments, label, milestone, assignee
// Issue detail, title, comments, label, milestone, assignee
let issueCell = app.staticTexts["Test issue 1 from integration tests"].firstMatch
issueCell.tap()

View file

@ -36,7 +36,7 @@ final class MergedInstanceUITests: ForgejoUITestBase {
XCTAssertTrue(addButton.waitForExistence(timeout: 15), "Instance list did not appear")
addButton.tap()
// Form is pre-filled from dev launch args scroll and tap login
// Form is pre-filled from dev launch args, scroll and tap login
app.swipeUp()
let loginButton = app.buttons["login-button"]

View file

@ -2,7 +2,7 @@ import XCTest
final class OverviewCreateMutatingUITests: ForgejoUITestBase {
// MARK: - Create Issue from Issues Overview (mutates creates an issue)
// MARK: - Create Issue from Issues Overview (mutates, creates an issue)
@MainActor
func testCreateIssueFromOverview() throws {

View file

@ -13,7 +13,7 @@ final class OverviewCreateUITests: ForgejoReadOnlyUITestBase {
func testCreatePRFromOverview() throws {
app.tabBars.buttons["Pull Requests"].tap()
// Default scope is "All involvement" testadmin is assigned to PR #4
// Default scope is "All involvement", testadmin is assigned to PR #4
XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 10))
// Tap floating create button

View file

@ -53,7 +53,7 @@ final class PaginationUITests: ForgejoReadOnlyUITestBase {
XCTAssertTrue(closedButton.waitForExistence(timeout: 5))
closedButton.tap()
// Only 1 closed issue (#3) should not show load-more
// Only 1 closed issue (#3), should not show load-more
XCTAssertTrue(app.staticTexts["Test issue 3 from integration tests"].waitForExistence(timeout: 10))
let loadMore = app.activityIndicators["load-more-indicator"]

View file

@ -14,7 +14,7 @@ final class PermissionUITests: ForgejoUITestBase {
]
}
// MARK: - Issue Detail read-only user
// MARK: - Issue Detail, read-only user
@MainActor
func testIssueDetailHidesActionsForReadOnlyUser() throws {
@ -43,7 +43,7 @@ final class PermissionUITests: ForgejoUITestBase {
XCTAssertFalse(toggleStateButton.exists, "Close/Reopen button should be hidden for read-only user")
}
// MARK: - PR Detail read-only user
// MARK: - PR Detail, read-only user
@MainActor
func testPRDetailHidesActionsForReadOnlyUser() throws {

View file

@ -29,7 +29,7 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase {
openButton.tap()
XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 10))
// PR detail title, branches
// PR detail, title, branches
let prCell = app.staticTexts["Add feature file"].firstMatch
prCell.tap()
@ -55,7 +55,7 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase {
XCTAssertTrue(app.staticTexts["testbot"].waitForExistence(timeout: 10))
}
// MARK: - Diff View context line has no comment button
// MARK: - Diff View, context line has no comment button
@MainActor
func testDiffViewContextLineHasNoCommentButton() throws {

View file

@ -33,7 +33,7 @@ final class RepositoryMutatingUITests: ForgejoUITestBase {
toggleButton.tap()
toggleButton.tap()
// File viewer tap hello.py
// File viewer, tap hello.py
let fileCell = app.staticTexts["hello.py"].firstMatch
XCTAssertTrue(fileCell.waitForExistence(timeout: 10))
fileCell.tap()

View file

@ -38,7 +38,7 @@ final class RepositoryUITests: ForgejoReadOnlyUITestBase {
searchField.buttons["Clear text"].tap()
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 10))
// Star toggle (last tap triggers NavigationLink, only tests tappability)
// Star toggle (last, tap triggers NavigationLink, only tests tappability)
let starButton = repoList.buttons["star-button"].firstMatch
XCTAssertTrue(starButton.waitForExistence(timeout: 5), "No star button found in repo list")
starButton.tap()
@ -130,7 +130,7 @@ final class RepositoryUITests: ForgejoReadOnlyUITestBase {
// Wait for repo detail to load
XCTAssertTrue(app.staticTexts["html-readme-repo"].waitForExistence(timeout: 10))
// Raw HTML tags should NOT appear as plain text they should be rendered
// Raw HTML tags should NOT appear as plain text, they should be rendered
XCTAssertFalse(
app.staticTexts["<details>"].waitForExistence(timeout: 5),
"Raw <details> tag should not be visible as plain text")

View file

@ -39,6 +39,7 @@ Forji is available on the Apple App Store for free: [Forji App Store](https://ap
- Create, edit, and close/reopen issues
- Manage labels, milestones, and assignees
- Comment with Markdown support
- Attach and view image and file attachments in descriptions and comments
### Pull Requests
- View PRs across all repositories or per-repo
@ -47,6 +48,7 @@ Forji is available on the Apple App Store for free: [Forji App Store](https://ap
- Submit reviews (comment, approve, request changes)
- Merge with merge commit, rebase, or squash
- Close, reopen, and edit PRs
- Attach and view image and file attachments in descriptions and comments
### Actions
- Browse Forgejo Actions workflows defined in a repository
@ -110,13 +112,13 @@ The Forji logo is based on the [Forgejo logo](https://codeberg.org/forgejo/forge
### Libraries
- [ForgejoKit](https://codeberg.org/secana/ForgejoKit) Forgejo API client (MIT)
- [Textual](https://github.com/gonzalezreal/textual) Markdown rendering (MIT)
- [HighlightSwift](https://github.com/appstefan/HighlightSwift) Code syntax highlighting (MIT)
- [mermaid](https://github.com/mermaid-js/mermaid) Diagram rendering (MIT)
- [marked](https://github.com/markedjs/marked) Markdown parser (MIT)
- [DOMPurify](https://github.com/cure53/DOMPurify) HTML sanitizer (Apache 2.0 / MPL 2.0)
- [github-markdown-css](https://github.com/sindresorhus/github-markdown-css) GitHub-style Markdown styling (MIT)
- [ForgejoKit](https://codeberg.org/secana/ForgejoKit), Forgejo API client (MIT)
- [Textual](https://github.com/gonzalezreal/textual), Markdown rendering (MIT)
- [HighlightSwift](https://github.com/appstefan/HighlightSwift), Code syntax highlighting (MIT)
- [mermaid](https://github.com/mermaid-js/mermaid), Diagram rendering (MIT)
- [marked](https://github.com/markedjs/marked), Markdown parser (MIT)
- [DOMPurify](https://github.com/cure53/DOMPurify), HTML sanitizer (Apache 2.0 / MPL 2.0)
- [github-markdown-css](https://github.com/sindresorhus/github-markdown-css), GitHub-style Markdown styling (MIT)
## Contributing