This commit is contained in:
Michael Freno
2026-01-27 14:12:24 -05:00
parent fda136f3d4
commit f8868c9253
31 changed files with 2030 additions and 1790 deletions

View File

@@ -0,0 +1,33 @@
//
// MockTimeProvider.swift
// GazeTests
//
// Mock time provider for deterministic timer testing.
//
import Foundation
@testable import Gaze
/// A mock time provider for deterministic testing.
/// Allows manual control over time in tests.
final class MockTimeProvider: TimeProviding, @unchecked Sendable {
private var currentTime: Date
init(startTime: Date = Date()) {
self.currentTime = startTime
}
func now() -> Date {
currentTime
}
/// Advances time by the specified interval
func advance(by interval: TimeInterval) {
currentTime = currentTime.addingTimeInterval(interval)
}
/// Sets the current time to a specific date
func setTime(_ date: Date) {
currentTime = date
}
}

View File

@@ -0,0 +1,65 @@
//
// TestServiceContainer.swift
// GazeTests
//
// Test-specific dependency injection container.
//
import Foundation
@testable import Gaze
/// A dependency injection container configured for testing.
/// Provides injectable dependencies and test-specific utilities.
@MainActor
final class TestServiceContainer {
/// The settings manager instance
private(set) var settingsManager: any SettingsProviding
/// The timer engine instance
private var _timerEngine: TimerEngine?
/// Time provider for deterministic testing
let timeProvider: TimeProviding
/// Creates a test container with default mock settings
convenience init() {
self.init(settings: AppSettings())
}
/// Creates a test container with custom settings
init(settings: AppSettings) {
self.settingsManager = EnhancedMockSettingsManager(settings: settings)
self.timeProvider = MockTimeProvider()
}
/// Creates a test container with a custom settings manager
init(settingsManager: any SettingsProviding) {
self.settingsManager = settingsManager
self.timeProvider = MockTimeProvider()
}
/// Gets or creates the timer engine for testing
var timerEngine: TimerEngine {
if let engine = _timerEngine {
return engine
}
let engine = TimerEngine(
settingsManager: settingsManager,
enforceModeService: nil,
timeProvider: timeProvider
)
_timerEngine = engine
return engine
}
/// Sets a custom timer engine
func setTimerEngine(_ engine: TimerEngine) {
_timerEngine = engine
}
/// Resets the container for test isolation
func reset() {
_timerEngine?.stop()
_timerEngine = nil
}
}

View File

@@ -14,31 +14,30 @@ final class ServiceContainerTests: XCTestCase {
func testProductionContainerCreation() {
let container = ServiceContainer.shared
XCTAssertFalse(container.isTestEnvironment)
XCTAssertNotNil(container.settingsManager)
XCTAssertNotNil(container.enforceModeService)
}
func testTestContainerCreation() {
let settings = AppSettings.onlyLookAwayEnabled
let container = ServiceContainer.forTesting(settings: settings)
let container = TestServiceContainer(settings: settings)
XCTAssertTrue(container.isTestEnvironment)
XCTAssertEqual(container.settingsManager.settings.lookAwayTimer.enabled, true)
XCTAssertEqual(container.settingsManager.settings.blinkTimer.enabled, false)
}
func testTimerEngineCreation() {
let container = ServiceContainer.forTesting()
let container = TestServiceContainer()
let timerEngine = container.timerEngine
XCTAssertNotNil(timerEngine)
// Second access should return the same instance
XCTAssertTrue(container.timerEngine === timerEngine)
XCTAssertTrue(container.timeProvider is MockTimeProvider)
}
func testCustomTimerEngineInjection() {
let container = ServiceContainer.forTesting()
let container = TestServiceContainer()
let mockSettings = EnhancedMockSettingsManager(settings: .shortIntervals)
let customEngine = TimerEngine(
settingsManager: mockSettings,
@@ -51,10 +50,10 @@ final class ServiceContainerTests: XCTestCase {
}
func testContainerReset() {
let container = ServiceContainer.forTesting()
let container = TestServiceContainer()
// Access timer engine to create it
_ = container.timerEngine
let existingEngine = container.timerEngine
// Reset should clear the timer engine
container.reset()
@@ -62,5 +61,6 @@ final class ServiceContainerTests: XCTestCase {
// Accessing again should create a new instance
let newEngine = container.timerEngine
XCTAssertNotNil(newEngine)
XCTAssertFalse(existingEngine === newEngine)
}
}

View File

@@ -202,28 +202,28 @@ extension AppSettings {
@MainActor
func createTestContainer(
settings: AppSettings = .defaults
) -> ServiceContainer {
return ServiceContainer.forTesting(settings: settings)
) -> TestServiceContainer {
return TestServiceContainer(settings: settings)
}
/// Creates a complete test environment with all mocks
@MainActor
struct TestEnvironment {
let container: ServiceContainer
let container: TestServiceContainer
let windowManager: MockWindowManager
let settingsManager: EnhancedMockSettingsManager
let timeProvider: MockTimeProvider
init(settings: AppSettings = .defaults) {
self.settingsManager = EnhancedMockSettingsManager(settings: settings)
self.container = ServiceContainer(settingsManager: settingsManager)
self.container = TestServiceContainer(settingsManager: settingsManager)
self.windowManager = MockWindowManager()
self.timeProvider = MockTimeProvider()
}
/// Creates an AppDelegate with all test dependencies
func createAppDelegate() -> AppDelegate {
return AppDelegate(serviceContainer: container, windowManager: windowManager)
return AppDelegate(serviceContainer: serviceContainer, windowManager: windowManager)
}
/// Resets all mock state
@@ -231,6 +231,13 @@ struct TestEnvironment {
windowManager.reset()
settingsManager.reset()
}
private var serviceContainer: ServiceContainer {
ServiceContainer(
settingsManager: settingsManager,
enforceModeService: EnforceModeService.shared
)
}
}
// MARK: - XCTest Extensions