Files
Reflect/Shared/UITestMode.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

142 lines
5.2 KiB
Swift

//
// UITestMode.swift
// Reflect (iOS)
//
// Handles launch arguments for UI testing mode.
// When --ui-testing is passed, the app uses deterministic settings.
//
import Foundation
#if canImport(UIKit)
import UIKit
#endif
enum UITestMode {
/// Whether the app was launched in UI testing mode
static var isUITesting: Bool {
ProcessInfo.processInfo.arguments.contains("--ui-testing")
}
/// Whether to reset all state before the test run
static var shouldResetState: Bool {
ProcessInfo.processInfo.arguments.contains("--reset-state")
}
/// Whether to disable animations for faster, more deterministic tests
static var disableAnimations: Bool {
ProcessInfo.processInfo.arguments.contains("--disable-animations")
}
/// Whether to bypass the subscription paywall
static var bypassSubscription: Bool {
ProcessInfo.processInfo.arguments.contains("--bypass-subscription")
}
/// Whether to skip onboarding
static var skipOnboarding: Bool {
ProcessInfo.processInfo.arguments.contains("--skip-onboarding")
}
/// Whether to force the trial to be expired (sets firstLaunchDate to 31 days ago)
static var expireTrial: Bool {
ProcessInfo.processInfo.arguments.contains("--expire-trial")
}
/// Seed fixture name if provided (via environment variable)
static var seedFixture: String? {
ProcessInfo.processInfo.environment["UI_TEST_FIXTURE"]
}
/// Apply all UI test mode settings. Called early in app startup.
@MainActor
static func configureIfNeeded() {
guard isUITesting else { return }
#if canImport(UIKit)
if disableAnimations {
UIView.setAnimationsEnabled(false)
}
#endif
if shouldResetState {
resetAppState()
}
if skipOnboarding {
GroupUserDefaults.groupDefaults.set(false, forKey: UserDefaultsStore.Keys.needsOnboarding.rawValue)
}
if expireTrial {
// Set firstLaunchDate to 31 days ago so the 30-day trial is expired.
// Must run BEFORE IAPManager.shared is accessed so the async status
// check sees the expired date.
let expiredDate = Calendar.current.date(byAdding: .day, value: -31, to: Date())!
GroupUserDefaults.groupDefaults.set(expiredDate, forKey: UserDefaultsStore.Keys.firstLaunchDate.rawValue)
GroupUserDefaults.groupDefaults.synchronize()
}
#if DEBUG
IAPManager.shared.bypassSubscription = bypassSubscription
// Reset subscription state to discard stale cached state from previous test runs.
// IAPManager.shared was already initialized (as @StateObject in ReflectApp) before
// configureIfNeeded runs, so it may have restored stale subscription data.
IAPManager.shared.resetForTesting()
#endif
// Seed fixture data if requested
if let fixture = seedFixture {
seedData(fixture: fixture)
}
}
/// Reset all user defaults and persisted state for a clean test run
@MainActor
private static func resetAppState() {
let defaults = GroupUserDefaults.groupDefaults
// Clear group user defaults using the suite domain name
defaults.removePersistentDomain(forName: Constants.currentGroupShareId)
// Explicitly clear subscription cache keys that may survive removePersistentDomain
// on app group suites (known reliability issue).
defaults.removeObject(forKey: UserDefaultsStore.Keys.cachedSubscriptionExpiration.rawValue)
defaults.removeObject(forKey: UserDefaultsStore.Keys.hasActiveSubscription.rawValue)
defaults.removeObject(forKey: UserDefaultsStore.Keys.firstLaunchDate.rawValue)
// Reset key defaults explicitly (true = fresh install state where onboarding is needed)
defaults.set(true, forKey: UserDefaultsStore.Keys.needsOnboarding.rawValue)
defaults.set(0, forKey: UserDefaultsStore.Keys.votingLayoutStyle.rawValue) // horizontal
defaults.synchronize()
// Clear standard user defaults
UserDefaults.standard.set(false, forKey: "debug_bypassSubscription")
// Clear all mood data
DataController.shared.clearDB()
}
/// Seed the database with fixture data for deterministic tests
@MainActor
private static func seedData(fixture: String) {
switch fixture {
case "single_mood":
// One entry for today with mood "Great"
DataController.shared.add(mood: .great, forDate: Calendar.current.startOfDay(for: Date()), entryType: .listView)
case "week_of_moods":
// One mood per day for the last 7 days
let moods: [Mood] = [.great, .good, .average, .bad, .horrible, .good, .great]
for (offset, mood) in moods.enumerated() {
let date = Calendar.current.date(byAdding: .day, value: -offset, to: Calendar.current.startOfDay(for: Date()))!
DataController.shared.add(mood: mood, forDate: date, entryType: .listView)
}
case "empty":
// No data already cleared in resetAppState
break
default:
break
}
}
}