diff --git a/Forji/Forji/Views/SearchableOverviewView.swift b/Forji/Forji/Views/SearchableOverviewView.swift index 97f9a75..3af9f45 100644 --- a/Forji/Forji/Views/SearchableOverviewView.swift +++ b/Forji/Forji/Views/SearchableOverviewView.swift @@ -240,27 +240,38 @@ struct SearchableOverviewView: View { } private func searchIssues(service: IssueService, page: Int, limit: Int) async throws -> [Issue] { + // Forgejo's search (q=) does not work with involvement scope flags + // (assigned, created, mentioned). When a search query is active, search + // globally by type and state only. The scoped view returns when the + // user clears the search. + if !searchText.isEmpty { + return try await service.searchIssues( + type: issueType, state: stateFilter.rawValue, query: searchText, + page: page, limit: limit, + ) + } + switch involvementScope { case .involved: - try await searchInvolved(service: service, page: page, limit: limit) + return try await searchInvolved(service: service, page: page, limit: limit) case .created: - try await service.searchIssues( - type: issueType, state: stateFilter.rawValue, query: searchText, + return try await service.searchIssues( + type: issueType, state: stateFilter.rawValue, page: page, limit: limit, created: true, ) case .assigned: - try await service.searchIssues( - type: issueType, state: stateFilter.rawValue, query: searchText, + return try await service.searchIssues( + type: issueType, state: stateFilter.rawValue, page: page, limit: limit, assigned: true, ) case .mentioned: - try await service.searchIssues( - type: issueType, state: stateFilter.rawValue, query: searchText, + return try await service.searchIssues( + type: issueType, state: stateFilter.rawValue, page: page, limit: limit, mentioned: true, ) case .reviewRequested: - try await service.searchIssues( - type: issueType, state: stateFilter.rawValue, query: searchText, + return try await service.searchIssues( + type: issueType, state: stateFilter.rawValue, page: page, limit: limit, reviewRequested: true, ) } @@ -273,14 +284,13 @@ struct SearchableOverviewView: View { private func searchInvolved(service: IssueService, page: Int, limit: Int) async throws -> [Issue] { let type = issueType let state = stateFilter.rawValue - let query = searchText let batches = try await withThrowingTaskGroup(of: [Issue].self) { group in for flag in involvedFlags { group.addTask { try await resilientFetch { try await service.searchIssues( - type: type, state: state, query: query, page: page, limit: limit, + type: type, state: state, page: page, limit: limit, assigned: flag == .assigned, created: flag == .created, mentioned: flag == .mentioned, reviewRequested: flag == .reviewRequested, ) diff --git a/Forji/ForjiUITests/IssueUITests.swift b/Forji/ForjiUITests/IssueUITests.swift index ee2ccd3..29ffd33 100644 --- a/Forji/ForjiUITests/IssueUITests.swift +++ b/Forji/ForjiUITests/IssueUITests.swift @@ -13,23 +13,16 @@ final class IssueUITests: ForgejoReadOnlyUITestBase { func testIssuesOverview() throws { app.tabBars.buttons["Issues"].tap() - // Issues list loads (may show pagination test issues first on page 1) - XCTAssertTrue(app.cells.firstMatch.waitForExistence(timeout: 10), "Issues should load") + // Wait for initial load (default scope: Open + All involvement) + XCTAssertTrue(app.cells.firstMatch.waitForExistence(timeout: 15), "Issues should load") - // Filter All via toolbar menu - let filterMenuButton = app.buttons["filter-menu-button"] - XCTAssertTrue(filterMenuButton.waitForExistence(timeout: 5)) - filterMenuButton.tap() - app.buttons["All"].firstMatch.tap() - XCTAssertTrue(app.cells.firstMatch.waitForExistence(timeout: 10), "Issues should load after filter change") - - // Search narrows results to specific issue (unique keyword avoids pagination test issues) + // Search narrows results to a specific issue let searchField = app.searchFields.firstMatch XCTAssertTrue(searchField.waitForExistence(timeout: 5)) searchField.tap() - searchField.typeText("integration") + searchField.typeText("Pagination test issue 25") - XCTAssertTrue(app.staticTexts["Test issue 1 from integration tests"].waitForExistence(timeout: 10)) + XCTAssertTrue(app.staticTexts["Pagination test issue 25"].waitForExistence(timeout: 15)) } // MARK: - Issues Overview Involvement Filter @@ -44,10 +37,10 @@ final class IssueUITests: ForgejoReadOnlyUITestBase { // Wait for list to load XCTAssertTrue(app.cells.firstMatch.waitForExistence(timeout: 10), "Issues should load") - // Default filter summary should show "Open · Created by you" + // Default filter summary should show "Open" (default scope is All involvement) let filterSummary = app.staticTexts["filter-summary"] XCTAssertTrue(filterSummary.waitForExistence(timeout: 5)) - XCTAssertEqual(filterSummary.label, "Open · Created by you") + XCTAssertEqual(filterSummary.label, "Open") let filterMenuButton = app.buttons["filter-menu-button"] @@ -63,11 +56,11 @@ final class IssueUITests: ForgejoReadOnlyUITestBase { sleep(2) XCTAssertTrue(filterSummary.label.contains("Mentioned")) - // Tap back to "All" scope via filter menu + // Tap "Created by you" via filter menu filterMenuButton.tap() - // The scope "All" button — use firstMatch since "All" also appears in State section - app.buttons["All"].firstMatch.tap() + app.buttons["Created by you"].tap() sleep(2) + XCTAssertTrue(filterSummary.label.contains("Created by you")) // "Review requested" should NOT be present for issues filterMenuButton.tap() @@ -113,7 +106,7 @@ final class IssueUITests: ForgejoReadOnlyUITestBase { allButtons.element(boundBy: 1).tap() XCTAssertTrue(filterSummary.waitForExistence(timeout: 5)) - XCTAssertEqual(filterSummary.label, "All · All", + XCTAssertEqual(filterSummary.label, "All · All involvement", "Filter summary should show All state and All scope") // The key assertion: items must actually load diff --git a/Forji/ForjiUITests/OverviewCreateUITests.swift b/Forji/ForjiUITests/OverviewCreateUITests.swift index 828774b..00d87a1 100644 --- a/Forji/ForjiUITests/OverviewCreateUITests.swift +++ b/Forji/ForjiUITests/OverviewCreateUITests.swift @@ -13,10 +13,7 @@ final class OverviewCreateUITests: ForgejoReadOnlyUITestBase { func testCreatePRFromOverview() throws { app.tabBars.buttons["Pull Requests"].tap() - // Test PRs were created by testbot — switch scope to "All" to see them - setOverviewScopeAll() - - // Wait for the PR list to load + // Default scope is "All involvement" — testadmin is assigned to PR #4 XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 10)) // Tap floating create button diff --git a/Forji/ForjiUITests/PullRequestUITests.swift b/Forji/ForjiUITests/PullRequestUITests.swift index 622b030..b9d727f 100644 --- a/Forji/ForjiUITests/PullRequestUITests.swift +++ b/Forji/ForjiUITests/PullRequestUITests.swift @@ -90,16 +90,8 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase { func testPullRequestsOverview() throws { app.tabBars.buttons["Pull Requests"].tap() - // Test PRs were created by testbot — switch scope to "All" to see them - setOverviewScopeAll() - - XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 10)) - - // Filter state to All via toolbar menu - let filterMenuButton = app.buttons["filter-menu-button"] - XCTAssertTrue(filterMenuButton.waitForExistence(timeout: 5)) - filterMenuButton.tap() - app.buttons["All"].firstMatch.tap() + // Wait for initial load to complete (testadmin is assigned to PR #4) + XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 15)) // Search let searchField = app.searchFields.firstMatch @@ -107,7 +99,7 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase { searchField.tap() searchField.typeText("feature") - XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 10)) + XCTAssertTrue(app.staticTexts["Add feature file"].waitForExistence(timeout: 15)) } // MARK: - Pull Requests Overview Involvement Filter @@ -119,10 +111,10 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase { // Reset filters to defaults (previous test may have changed them) resetOverviewFilters() - // Default filter summary should show "Open · Created by you" + // Default filter summary should show "Open" (default scope is All involvement) let filterSummary = app.staticTexts["filter-summary"] XCTAssertTrue(filterSummary.waitForExistence(timeout: 5)) - XCTAssertEqual(filterSummary.label, "Open · Created by you") + XCTAssertEqual(filterSummary.label, "Open") let filterMenuButton = app.buttons["filter-menu-button"] @@ -138,10 +130,11 @@ final class PullRequestUITests: ForgejoReadOnlyUITestBase { sleep(2) XCTAssertTrue(filterSummary.label.contains("Mentioned")) - // Tap back to "All" scope via filter menu + // Tap "Created by you" via filter menu filterMenuButton.tap() - app.buttons["All"].firstMatch.tap() + app.buttons["Created by you"].tap() sleep(2) + XCTAssertTrue(filterSummary.label.contains("Created by you")) // "Review requested" SHOULD be present for PRs filterMenuButton.tap() diff --git a/Forji/ForjiUITests/UITestNavigating.swift b/Forji/ForjiUITests/UITestNavigating.swift index b159d1d..b253e6b 100644 --- a/Forji/ForjiUITests/UITestNavigating.swift +++ b/Forji/ForjiUITests/UITestNavigating.swift @@ -28,16 +28,14 @@ extension UITestNavigating { picker.buttons["Pull Requests"].tap() } - /// Resets the overview filter to Open + Created by you (the app defaults). + /// Resets the overview filter to Open + All involvement (the app defaults). func resetOverviewFilters() { let filterMenuButton = app.buttons["filter-menu-button"] guard filterMenuButton.waitForExistence(timeout: 5) else { return } filterMenuButton.tap() app.buttons["Open"].tap() sleep(1) - filterMenuButton.tap() - app.buttons["Created by you"].tap() - sleep(1) + setOverviewScopeAll() } /// Switches the overview scope to "All" to show items from all users. diff --git a/integration/setup.sh b/integration/setup.sh index 06c83c0..953f69f 100755 --- a/integration/setup.sh +++ b/integration/setup.sh @@ -60,17 +60,27 @@ curl -sf -X POST "$BASE_URL/api/v1/user/repos" \ wait echo "Repositories created." -# Archive the archived-repo +# Archive the archived-repo (retry — the repo may not be ready immediately after creation) echo "Archiving archived-repo..." -curl -sf -X PATCH "$BASE_URL/api/v1/repos/$ADMIN_USER/archived-repo" \ - -u "$ADMIN_AUTH" \ - -H "Content-Type: application/json" \ - -d '{"archived": true}' +for attempt in 1 2 3; do + if curl -sf -X PATCH "$BASE_URL/api/v1/repos/$ADMIN_USER/archived-repo" \ + -u "$ADMIN_AUTH" \ + -H "Content-Type: application/json" \ + -d '{"archived": true}'; then + break + fi + echo " Archive attempt $attempt failed, retrying..." + sleep 1 +done # Update html-readme-repo README with HTML content echo "Updating html-readme-repo README..." -README_SHA=$(curl -sf "$BASE_URL/api/v1/repos/$ADMIN_USER/html-readme-repo/contents/README.md" \ - -u "$ADMIN_AUTH" | python3 -c "import sys,json; print(json.load(sys.stdin)['sha'])") +for attempt in 1 2 3; do + README_SHA=$(curl -sf "$BASE_URL/api/v1/repos/$ADMIN_USER/html-readme-repo/contents/README.md" \ + -u "$ADMIN_AUTH" | python3 -c "import sys,json; print(json.load(sys.stdin)['sha'])" 2>/dev/null) && break + echo " README fetch attempt $attempt failed, retrying..." + sleep 1 +done README_PAYLOAD=$(python3 -c " import base64, json content = '''# HTML Readme Test