Files
Sportstime/SportsTimeUITests/Tests/SettingsTests.swift
2026-02-20 00:52:07 -06:00

291 lines
10 KiB
Swift

//
// SettingsTests.swift
// SportsTimeUITests
//
// Verifies the Settings screen loads, displays version, and shows all sections.
// QA Sheet: F-123, F-124, F-125, F-126, F-127, F-128, F-133, F-134, F-135, F-138, F-139
//
import XCTest
final class SettingsTests: BaseUITestCase {
/// F-062: Settings shows app version.
@MainActor
func testF062_SettingsShowsVersion() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
settings.assertVersionDisplayed()
captureScreenshot(named: "F062-Settings-Version")
}
/// F-063: Settings key sections are present (Subscription, Privacy, About).
@MainActor
func testF063_SettingsSectionsPresent() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
// Assert: Key sections exist
XCTAssertTrue(settings.subscriptionSection.waitForExistence(
timeout: BaseUITestCase.shortTimeout),
"Subscription section should exist")
// SwiftUI List renders as UICollectionView on iOS 26
settings.privacySection.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.privacySection.exists, "Privacy section should exist")
settings.aboutSection.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.aboutSection.exists, "About section should exist")
}
/// F-075: Subscription section shows correct content for current user tier.
@MainActor
func testF075_SubscriptionSectionContent() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
// Subscription section header should exist
XCTAssertTrue(settings.subscriptionSection.waitForExistence(
timeout: BaseUITestCase.shortTimeout),
"Subscription section should exist")
// In debug/UI testing mode, debugProOverride is true Pro user
// So we expect "SportsTime Pro" text and no "Upgrade to Pro" button
let proLabel = app.staticTexts["SportsTime Pro"]
if proLabel.exists {
// Pro user path
XCTAssertTrue(proLabel.exists, "Pro label should be visible for Pro user")
} else {
// Free user path "Upgrade to Pro" and "Restore Purchases" should exist
settings.upgradeProButton.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.upgradeProButton.exists,
"Upgrade to Pro button should exist for free user")
settings.restorePurchasesButton.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.restorePurchasesButton.exists,
"Restore Purchases button should exist for free user")
}
captureScreenshot(named: "F075-Settings-Subscription")
}
// MARK: - Appearance Mode (F-125, F-126, F-127)
/// Helper: navigates to Settings and scrolls to Appearance section.
@MainActor
private func navigateToAppearance() -> SettingsScreen {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
settings.appearanceSection.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.appearanceSection.exists, "Appearance section should exist")
return settings
}
/// F-125: Appearance - Light mode can be selected.
@MainActor
func testF125_AppearanceLightMode() {
let settings = navigateToAppearance()
let lightBtn = settings.appearanceButton("Light")
lightBtn.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(lightBtn.exists, "Light mode button should exist")
lightBtn.tap()
captureScreenshot(named: "F125-Appearance-Light")
}
/// F-126: Appearance - Dark mode can be selected.
@MainActor
func testF126_AppearanceDarkMode() {
let settings = navigateToAppearance()
let darkBtn = settings.appearanceButton("Dark")
darkBtn.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(darkBtn.exists, "Dark mode button should exist")
darkBtn.tap()
captureScreenshot(named: "F126-Appearance-Dark")
}
/// F-127: Appearance - System mode can be selected.
@MainActor
func testF127_AppearanceSystemMode() {
let settings = navigateToAppearance()
let systemBtn = settings.appearanceButton("System")
systemBtn.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(systemBtn.exists, "System mode button should exist")
systemBtn.tap()
captureScreenshot(named: "F127-Appearance-System")
}
// MARK: - Toggle Animations (F-128)
/// F-128: Toggle animations on/off in Settings.
@MainActor
func testF128_ToggleAnimations() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
let toggle = settings.animationsToggle
toggle.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(toggle.exists, "Animations toggle should exist")
// Capture initial state
let initialValue = toggle.value as? String
// On iOS 26, switches in List rows need a coordinate-based tap
// to ensure the tap lands on the switch control itself
let switchCoord = toggle.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5))
switchCoord.tap()
// Small wait for the toggle animation to complete
sleep(1)
// Value should have changed
let newValue = toggle.value as? String
XCTAssertNotEqual(initialValue, newValue,
"Toggle value should change after tap (was '\(initialValue ?? "nil")', now '\(newValue ?? "nil")')")
captureScreenshot(named: "F128-AnimationsToggled")
}
// MARK: - Manual Sync (F-133)
/// F-133: Tap "Sync Now" button triggers sync.
@MainActor
func testF133_ManualSyncTrigger() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
let syncButton = settings.syncNowButton
syncButton.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(syncButton.exists, "Sync Now button should exist")
syncButton.tap()
// After tapping, no crash is the primary assertion.
// Sync may show a progress indicator or message briefly.
captureScreenshot(named: "F133-ManualSync")
}
// MARK: - Sync Logs (F-134)
/// F-134: View Sync Logs button opens the log viewer sheet.
@MainActor
func testF134_ViewSyncLogs() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
// Scroll to and tap "View Sync Logs" button
let syncLogsButton = app.buttons.matching(NSPredicate(
format: "label CONTAINS 'Sync Logs'"
)).firstMatch
syncLogsButton.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(syncLogsButton.exists, "View Sync Logs button should exist")
syncLogsButton.tap()
// Sheet should appear look for navigation bar or content
let sheetContent = app.navigationBars.firstMatch
XCTAssertTrue(sheetContent.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Sync logs sheet should appear")
captureScreenshot(named: "F134-SyncLogs")
}
// MARK: - Reset to Defaults (F-138, F-139)
/// F-138: Reset to Defaults triggers confirmation and resets settings.
@MainActor
func testF138_ResetToDefaults() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
settings.resetButton.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.resetButton.exists, "Reset button should exist")
settings.resetButton.tap()
// Confirmation alert should appear
let alert = app.alerts.firstMatch
XCTAssertTrue(alert.waitForExistence(timeout: BaseUITestCase.shortTimeout),
"Reset confirmation alert should appear")
// Confirm the reset
let confirmButton = alert.buttons["Reset"]
if confirmButton.exists {
confirmButton.tap()
} else {
// Fallback: tap the destructive action (could be "Reset" or "OK")
alert.buttons.element(boundBy: 1).tap()
}
captureScreenshot(named: "F138-ResetToDefaults")
}
/// F-139: Reset to Defaults - cancel leaves settings unchanged.
@MainActor
func testF139_ResetToDefaultsCancel() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
settings.resetButton.scrollIntoView(in: app.collectionViews.firstMatch)
settings.resetButton.tap()
// Confirmation alert should appear
let alert = app.alerts.firstMatch
XCTAssertTrue(alert.waitForExistence(timeout: BaseUITestCase.shortTimeout),
"Reset confirmation alert should appear")
// Cancel the reset
let cancelButton = alert.buttons["Cancel"]
XCTAssertTrue(cancelButton.exists, "Cancel button should exist on alert")
cancelButton.tap()
// Settings screen should still be visible check reset button
// (it's already in view since we just tapped it)
XCTAssertTrue(settings.resetButton.waitForExistence(
timeout: BaseUITestCase.shortTimeout),
"Settings should still be displayed after cancelling reset")
captureScreenshot(named: "F139-ResetCancel")
}
}