291 lines
10 KiB
Swift
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")
|
|
}
|
|
}
|