Forji/Forji/ForjiUITests/RepositoryUITests.swift
2026-06-15 12:19:00 +02:00

204 lines
8.2 KiB
Swift

import XCTest
final class RepositoryUITests: ForgejoReadOnlyUITestBase {
// MARK: - Repository List (repos, star, filter, search)
@MainActor
func testRepositoryList() throws {
app.tabBars.buttons["Repositories"].tap()
let repoList = app.collectionViews["repo-list"]
XCTAssertTrue(repoList.waitForExistence(timeout: 10))
// Both repos visible
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 5))
XCTAssertTrue(app.staticTexts["test-repo-2"].waitForExistence(timeout: 5))
// Filter by Starred
let starredButton = app.buttons["Starred"]
XCTAssertTrue(starredButton.waitForExistence(timeout: 5))
starredButton.tap()
// Switch back to All
let allButton = app.buttons["All"]
XCTAssertTrue(allButton.waitForExistence(timeout: 5))
allButton.tap()
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 10))
// Search
let searchField = app.searchFields.firstMatch
XCTAssertTrue(searchField.waitForExistence(timeout: 5))
searchField.tap()
searchField.typeText("test-repo-2")
XCTAssertTrue(app.staticTexts["test-repo-2"].waitForExistence(timeout: 10))
// Clear search to restore full list
searchField.buttons["Clear text"].tap()
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 10))
// 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()
}
// MARK: - Archived Badge
@MainActor
func testArchivedBadgeInRepoList() throws {
app.tabBars.buttons["Repositories"].tap()
let repoList = app.collectionViews["repo-list"]
XCTAssertTrue(repoList.waitForExistence(timeout: 10))
// Archived repo should show the "Archived" badge
XCTAssertTrue(app.staticTexts["archived-repo"].waitForExistence(timeout: 5), "archived-repo should be visible in repo list")
XCTAssertTrue(app.staticTexts["Archived"].exists, "Archived badge should be visible for archived-repo")
// Only one Archived badge should exist in the list
let archivedBadges = app.staticTexts.matching(identifier: "Archived")
XCTAssertEqual(archivedBadges.count, 1, "Only one Archived badge should be visible (for archived-repo)")
}
@MainActor
func testArchivedBadgeInRepoDetail() throws {
navigateToRepoDetail("archived-repo")
// The detail view header should show the "Archived" badge
XCTAssertTrue(app.staticTexts["Archived"].waitForExistence(timeout: 10), "Archived badge should be visible in repo detail")
}
@MainActor
func testNoArchivedBadgeForNonArchivedRepo() throws {
navigateToRepoDetail("test-repo")
// Wait for the detail view to load
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 10))
// The "Archived" badge should not appear
XCTAssertFalse(app.staticTexts["Archived"].exists, "Archived badge should not be visible for non-archived repo")
}
// MARK: - Repository Filter Menu
@MainActor
func testFilterHidesArchivedRepos() throws {
app.tabBars.buttons["Repositories"].tap()
let repoList = app.collectionViews["repo-list"]
XCTAssertTrue(repoList.waitForExistence(timeout: 10))
XCTAssertTrue(app.staticTexts["archived-repo"].waitForExistence(timeout: 5))
// Open filter menu and disable archived repos
let filterButton = app.buttons["filter-menu-button"]
XCTAssertTrue(filterButton.waitForExistence(timeout: 5))
filterButton.tap()
let archivedToggle = app.buttons["Archived"]
XCTAssertTrue(archivedToggle.waitForExistence(timeout: 5))
archivedToggle.tap()
// Archived repo should be hidden, non-archived repos still visible
XCTAssertTrue(app.staticTexts["test-repo"].waitForExistence(timeout: 5))
XCTAssertFalse(app.staticTexts["archived-repo"].waitForExistence(timeout: 2),
"archived-repo should be hidden when archived filter is off")
// Re-enable archived repos
filterButton.tap()
let archivedToggleAgain = app.buttons["Archived"]
XCTAssertTrue(archivedToggleAgain.waitForExistence(timeout: 5))
archivedToggleAgain.tap()
// Archived repo should be visible again
XCTAssertTrue(app.staticTexts["archived-repo"].waitForExistence(timeout: 5),
"archived-repo should reappear when archived filter is on")
}
override func tearDown() {
navigateBackToHome()
super.tearDown()
}
// MARK: - HTML in Markdown Rendering
@MainActor
func testHTMLInMarkdownRendering() throws {
navigateToRepoDetail("html-readme-repo")
// 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
XCTAssertFalse(
app.staticTexts["<details>"].waitForExistence(timeout: 5),
"Raw <details> tag should not be visible as plain text")
XCTAssertFalse(
app.staticTexts["<table>"].waitForExistence(timeout: 2),
"Raw <table> tag should not be visible as plain text")
XCTAssertFalse(
app.staticTexts["<kbd>"].waitForExistence(timeout: 2),
"Raw <kbd> tag should not be visible as plain text")
}
// MARK: - HTML Sanitization
@MainActor
func testDangerousHTMLIsStripped() throws {
navigateToRepoDetail("html-readme-repo")
XCTAssertTrue(app.staticTexts["html-readme-repo"].waitForExistence(timeout: 10))
// Script tags must be stripped by DOMPurify
XCTAssertFalse(
app.staticTexts["alert(\"xss\")"].waitForExistence(timeout: 5),
"Script content should be stripped by DOMPurify")
// Iframes must be stripped
XCTAssertFalse(
app.staticTexts["<iframe"].waitForExistence(timeout: 2),
"Iframe tags should be stripped by DOMPurify")
// onclick attributes must be stripped (the div text may render, but not as a clickable handler)
// Verify the safe content still renders
XCTAssertFalse(
app.staticTexts["onclick"].waitForExistence(timeout: 2),
"onclick attributes should be stripped by DOMPurify")
}
// MARK: - Branch Selector
@MainActor
func testBranchSelector() throws {
navigateToRepoDetail()
// Branch selector should exist and show "main" by default
let branchSelector = app.buttons["branch-selector"]
XCTAssertTrue(branchSelector.waitForExistence(timeout: 10), "Branch selector not found")
XCTAssertTrue(branchSelector.label.contains("main"), "Branch selector should show 'main' initially")
// Verify main branch files are visible
XCTAssertTrue(app.staticTexts["hello.py"].waitForExistence(timeout: 10))
// Switch to feature-branch via branch picker sheet
branchSelector.tap()
let featureBranchOption = app.buttons["branch-option-feature-branch"]
XCTAssertTrue(featureBranchOption.waitForExistence(timeout: 5), "feature-branch option not found")
featureBranchOption.tap()
// feature.txt should appear (only exists on feature-branch)
XCTAssertTrue(app.staticTexts["feature.txt"].waitForExistence(timeout: 10), "feature.txt should be visible on feature-branch")
// Switch back to main
let updatedSelector = app.buttons["branch-selector"]
XCTAssertTrue(updatedSelector.waitForExistence(timeout: 5))
updatedSelector.tap()
let mainBranchOption = app.buttons["branch-option-main"]
XCTAssertTrue(mainBranchOption.waitForExistence(timeout: 5), "main option not found")
mainBranchOption.tap()
// feature.txt should no longer be visible, hello.py should be back
XCTAssertTrue(app.staticTexts["hello.py"].waitForExistence(timeout: 10), "hello.py should be visible on main")
}
}