Files
Sportstime/SportsTimeUITests/Tests/SettingsTests.swift
Trey t dc142bd14b feat: expand XCUITest coverage to 54 QA scenarios with accessibility IDs and fix test failures
Add 22 new UI tests across 8 test files covering Home, Schedule, Progress,
Settings, TabNavigation, TripSaving, and TripOptions. Add accessibility
identifiers to 11 view files for test element discovery. Fix sport chip
assertion logic (all sports start selected, tap deselects), scroll container
issues on iOS 26 nested ScrollViews, toggle interaction, and delete trip flow.
Update QA coverage map from 32 to 54 automated test cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 19:44:22 -06:00

241 lines
8.6 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-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: - 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")
}
}