mirror of
https://codeberg.org/secana/Forji.git
synced 2026-07-04 01:35:46 -07:00
204 lines
8.2 KiB
Swift
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")
|
|
}
|
|
}
|