// // 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") } }