general: testability enhancements

This commit is contained in:
Michael Freno
2026-01-15 09:23:17 -05:00
parent 429d4ff32e
commit 5dc223ec96
17 changed files with 833 additions and 215 deletions

View File

@@ -30,11 +30,17 @@ final class MockSettingsManager: ObservableObject, SettingsProviding {
var saveCallCount = 0
var loadCallCount = 0
var resetToDefaultsCallCount = 0
var saveImmediatelyCallCount = 0
/// Track timer configuration updates for verification
var timerConfigurationUpdates: [(TimerType, TimerConfiguration)] = []
init(settings: AppSettings = .defaults) {
self.settings = settings
}
// MARK: - SettingsProviding conformance
func timerConfiguration(for type: TimerType) -> TimerConfiguration {
guard let keyPath = timerConfigKeyPaths[type] else {
preconditionFailure("Unknown timer type: \(type)")
@@ -47,6 +53,7 @@ final class MockSettingsManager: ObservableObject, SettingsProviding {
preconditionFailure("Unknown timer type: \(type)")
}
settings[keyPath: keyPath] = configuration
timerConfigurationUpdates.append((type, configuration))
}
func allTimerConfigurations() -> [TimerType: TimerConfiguration] {
@@ -62,7 +69,7 @@ final class MockSettingsManager: ObservableObject, SettingsProviding {
}
func saveImmediately() {
saveCallCount += 1
saveImmediatelyCallCount += 1
}
func load() {
@@ -73,4 +80,81 @@ final class MockSettingsManager: ObservableObject, SettingsProviding {
resetToDefaultsCallCount += 1
settings = .defaults
}
// MARK: - Test helper methods
/// Resets all call tracking counters
func resetCallTracking() {
saveCallCount = 0
loadCallCount = 0
resetToDefaultsCallCount = 0
saveImmediatelyCallCount = 0
timerConfigurationUpdates = []
}
/// Creates settings with all timers enabled
static func withAllTimersEnabled() -> MockSettingsManager {
var settings = AppSettings.defaults
settings.lookAwayTimer.enabled = true
settings.blinkTimer.enabled = true
settings.postureTimer.enabled = true
return MockSettingsManager(settings: settings)
}
/// Creates settings with all timers disabled
static func withAllTimersDisabled() -> MockSettingsManager {
var settings = AppSettings.defaults
settings.lookAwayTimer.enabled = false
settings.blinkTimer.enabled = false
settings.postureTimer.enabled = false
return MockSettingsManager(settings: settings)
}
/// Creates settings with onboarding completed
static func withOnboardingCompleted() -> MockSettingsManager {
var settings = AppSettings.defaults
settings.hasCompletedOnboarding = true
return MockSettingsManager(settings: settings)
}
/// Creates settings with custom timer intervals (in seconds)
static func withTimerIntervals(
lookAway: Int = 20 * 60,
blink: Int = 7 * 60,
posture: Int = 30 * 60
) -> MockSettingsManager {
var settings = AppSettings.defaults
settings.lookAwayTimer.intervalSeconds = lookAway
settings.blinkTimer.intervalSeconds = blink
settings.postureTimer.intervalSeconds = posture
return MockSettingsManager(settings: settings)
}
/// Enables a specific timer
func enableTimer(_ type: TimerType) {
guard let keyPath = timerConfigKeyPaths[type] else { return }
settings[keyPath: keyPath].enabled = true
}
/// Disables a specific timer
func disableTimer(_ type: TimerType) {
guard let keyPath = timerConfigKeyPaths[type] else { return }
settings[keyPath: keyPath].enabled = false
}
/// Sets a specific timer's interval
func setTimerInterval(_ type: TimerType, seconds: Int) {
guard let keyPath = timerConfigKeyPaths[type] else { return }
settings[keyPath: keyPath].intervalSeconds = seconds
}
/// Adds a user timer
func addUserTimer(_ timer: UserTimer) {
settings.userTimers.append(timer)
}
/// Removes all user timers
func clearUserTimers() {
settings.userTimers = []
}
}

View File

@@ -0,0 +1,101 @@
//
// MockWindowManager.swift
// GazeTests
//
// A mock implementation of WindowManaging for isolated unit testing.
//
import SwiftUI
@testable import Gaze
/// A mock implementation of WindowManaging that doesn't create real windows.
/// This allows tests to run in complete isolation without affecting the UI.
@MainActor
final class MockWindowManager: WindowManaging {
// MARK: - State tracking
var isOverlayReminderVisible: Bool = false
var isSubtleReminderVisible: Bool = false
// MARK: - Call tracking for verification
var showReminderWindowCalls: [(windowType: ReminderWindowType, viewType: String)] = []
var dismissOverlayReminderCallCount = 0
var dismissSubtleReminderCallCount = 0
var dismissAllRemindersCallCount = 0
var showSettingsCalls: [Int] = []
var showOnboardingCallCount = 0
/// The last window type shown
var lastShownWindowType: ReminderWindowType?
// MARK: - WindowManaging conformance
func showReminderWindow<Content: View>(_ content: Content, windowType: ReminderWindowType) {
let viewType = String(describing: type(of: content))
showReminderWindowCalls.append((windowType: windowType, viewType: viewType))
lastShownWindowType = windowType
switch windowType {
case .overlay:
isOverlayReminderVisible = true
case .subtle:
isSubtleReminderVisible = true
}
}
func dismissOverlayReminder() {
dismissOverlayReminderCallCount += 1
isOverlayReminderVisible = false
}
func dismissSubtleReminder() {
dismissSubtleReminderCallCount += 1
isSubtleReminderVisible = false
}
func dismissAllReminders() {
dismissAllRemindersCallCount += 1
isOverlayReminderVisible = false
isSubtleReminderVisible = false
}
func showSettings(settingsManager: any SettingsProviding, initialTab: Int) {
showSettingsCalls.append(initialTab)
}
func showOnboarding(settingsManager: any SettingsProviding) {
showOnboardingCallCount += 1
}
// MARK: - Test helpers
/// Resets all call tracking counters
func resetCallTracking() {
showReminderWindowCalls = []
dismissOverlayReminderCallCount = 0
dismissSubtleReminderCallCount = 0
dismissAllRemindersCallCount = 0
showSettingsCalls = []
showOnboardingCallCount = 0
lastShownWindowType = nil
isOverlayReminderVisible = false
isSubtleReminderVisible = false
}
/// Returns the number of overlay windows shown
var overlayWindowsShownCount: Int {
showReminderWindowCalls.filter { $0.windowType == .overlay }.count
}
/// Returns the number of subtle windows shown
var subtleWindowsShownCount: Int {
showReminderWindowCalls.filter { $0.windowType == .subtle }.count
}
/// Checks if a specific view type was shown
func wasViewShown(containing typeName: String) -> Bool {
showReminderWindowCalls.contains { $0.viewType.contains(typeName) }
}
}