general: basic cleanup
This commit is contained in:
@@ -6,92 +6,89 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import Gaze
|
||||
|
||||
@MainActor
|
||||
final class AppDelegateTestabilityTests: XCTestCase {
|
||||
|
||||
|
||||
var testEnv: TestEnvironment!
|
||||
|
||||
|
||||
override func setUp() async throws {
|
||||
testEnv = TestEnvironment(settings: .onboardingCompleted)
|
||||
}
|
||||
|
||||
|
||||
override func tearDown() async throws {
|
||||
testEnv = nil
|
||||
}
|
||||
|
||||
|
||||
func testAppDelegateCreationWithMocks() {
|
||||
let appDelegate = testEnv.createAppDelegate()
|
||||
|
||||
|
||||
XCTAssertNotNil(appDelegate)
|
||||
}
|
||||
|
||||
|
||||
func testWindowManagerReceivesReminderEvents() async throws {
|
||||
let appDelegate = testEnv.createAppDelegate()
|
||||
|
||||
// Simulate app launch
|
||||
|
||||
let notification = Notification(name: NSApplication.didFinishLaunchingNotification)
|
||||
appDelegate.applicationDidFinishLaunching(notification)
|
||||
|
||||
// Give time for setup
|
||||
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
|
||||
|
||||
// Trigger a reminder through timer engine
|
||||
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
if let timerEngine = appDelegate.timerEngine {
|
||||
let timerId = TimerIdentifier.builtIn(.blink)
|
||||
timerEngine.triggerReminder(for: timerId)
|
||||
|
||||
// Give time for reminder to propagate
|
||||
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
|
||||
|
||||
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Verify window manager received the show command
|
||||
XCTAssertTrue(testEnv.windowManager.didPerformOperation(.showSubtleReminder))
|
||||
} else {
|
||||
XCTFail("TimerEngine not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testSettingsChangesPropagate() async throws {
|
||||
let appDelegate = testEnv.createAppDelegate()
|
||||
|
||||
|
||||
// Change a setting
|
||||
testEnv.settingsManager.settings.lookAwayTimer.enabled = false
|
||||
|
||||
// Give time for observation
|
||||
try await Task.sleep(nanoseconds: 50_000_000) // 50ms
|
||||
|
||||
|
||||
try await Task.sleep(for: .milliseconds(50))
|
||||
|
||||
// Verify the change propagated
|
||||
XCTAssertFalse(testEnv.settingsManager.settings.lookAwayTimer.enabled)
|
||||
}
|
||||
|
||||
|
||||
func testOpenSettingsUsesWindowManager() {
|
||||
let appDelegate = testEnv.createAppDelegate()
|
||||
|
||||
|
||||
appDelegate.openSettings(tab: 2)
|
||||
|
||||
|
||||
// Give time for async dispatch
|
||||
let expectation = XCTestExpectation(description: "Settings opened")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
XCTAssertTrue(self.testEnv.windowManager.didPerformOperation(.showSettings(initialTab: 2)))
|
||||
XCTAssertTrue(
|
||||
self.testEnv.windowManager.didPerformOperation(.showSettings(initialTab: 2)))
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
}
|
||||
|
||||
|
||||
func testOpenOnboardingUsesWindowManager() {
|
||||
let appDelegate = testEnv.createAppDelegate()
|
||||
|
||||
|
||||
appDelegate.openOnboarding()
|
||||
|
||||
|
||||
// Give time for async dispatch
|
||||
let expectation = XCTestExpectation(description: "Onboarding opened")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
XCTAssertTrue(self.testEnv.windowManager.didPerformOperation(.showOnboarding))
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
|
||||
wait(for: [expectation], timeout: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,36 +7,37 @@
|
||||
|
||||
import Combine
|
||||
import XCTest
|
||||
|
||||
@testable import Gaze
|
||||
|
||||
@MainActor
|
||||
final class TimerEngineTests: XCTestCase {
|
||||
|
||||
|
||||
var testEnv: TestEnvironment!
|
||||
var timerEngine: TimerEngine!
|
||||
var cancellables: Set<AnyCancellable>!
|
||||
|
||||
|
||||
override func setUp() async throws {
|
||||
testEnv = TestEnvironment(settings: .defaults)
|
||||
timerEngine = testEnv.container.timerEngine
|
||||
cancellables = []
|
||||
}
|
||||
|
||||
|
||||
override func tearDown() async throws {
|
||||
timerEngine?.stop()
|
||||
cancellables = nil
|
||||
timerEngine = nil
|
||||
testEnv = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Initialization Tests
|
||||
|
||||
|
||||
func testTimerEngineInitialization() {
|
||||
XCTAssertNotNil(timerEngine)
|
||||
XCTAssertEqual(timerEngine.timerStates.count, 0)
|
||||
XCTAssertNil(timerEngine.activeReminder)
|
||||
}
|
||||
|
||||
|
||||
func testTimerEngineWithCustomTimeProvider() {
|
||||
let timeProvider = MockTimeProvider()
|
||||
let engine = TimerEngine(
|
||||
@@ -44,174 +45,173 @@ final class TimerEngineTests: XCTestCase {
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
|
||||
XCTAssertNotNil(engine)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Start/Stop Tests
|
||||
|
||||
|
||||
func testStartTimers() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
// Should create timer states for enabled timers
|
||||
XCTAssertGreaterThan(timerEngine.timerStates.count, 0)
|
||||
}
|
||||
|
||||
|
||||
func testStopTimers() {
|
||||
timerEngine.start()
|
||||
let initialCount = timerEngine.timerStates.count
|
||||
XCTAssertGreaterThan(initialCount, 0)
|
||||
|
||||
|
||||
timerEngine.stop()
|
||||
|
||||
|
||||
// Timers should be cleared
|
||||
XCTAssertEqual(timerEngine.timerStates.count, 0)
|
||||
}
|
||||
|
||||
|
||||
func testRestartTimers() {
|
||||
timerEngine.start()
|
||||
let firstCount = timerEngine.timerStates.count
|
||||
|
||||
|
||||
timerEngine.stop()
|
||||
XCTAssertEqual(timerEngine.timerStates.count, 0)
|
||||
|
||||
|
||||
timerEngine.start()
|
||||
let secondCount = timerEngine.timerStates.count
|
||||
|
||||
|
||||
XCTAssertEqual(firstCount, secondCount)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Pause/Resume Tests
|
||||
|
||||
|
||||
func testPauseAllTimers() {
|
||||
timerEngine.start()
|
||||
timerEngine.pause()
|
||||
|
||||
|
||||
for (_, state) in timerEngine.timerStates {
|
||||
XCTAssertTrue(state.isPaused)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testResumeAllTimers() {
|
||||
timerEngine.start()
|
||||
timerEngine.pause()
|
||||
timerEngine.resume()
|
||||
|
||||
|
||||
for (_, state) in timerEngine.timerStates {
|
||||
XCTAssertFalse(state.isPaused)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testPauseSpecificTimer() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.pauseTimer(identifier: firstTimer)
|
||||
|
||||
|
||||
let state = timerEngine.timerStates[firstTimer]
|
||||
XCTAssertTrue(state?.isPaused ?? false)
|
||||
}
|
||||
|
||||
|
||||
func testResumeSpecificTimer() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.pauseTimer(identifier: firstTimer)
|
||||
XCTAssertTrue(timerEngine.isTimerPaused(firstTimer))
|
||||
|
||||
|
||||
timerEngine.resumeTimer(identifier: firstTimer)
|
||||
XCTAssertFalse(timerEngine.isTimerPaused(firstTimer))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Skip Tests
|
||||
|
||||
|
||||
func testSkipNext() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.skipNext(identifier: firstTimer)
|
||||
|
||||
|
||||
// Timer should be reset
|
||||
XCTAssertNotNil(timerEngine.timerStates[firstTimer])
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Reminder Tests
|
||||
|
||||
|
||||
func testTriggerReminder() async throws {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.triggerReminder(for: firstTimer)
|
||||
|
||||
// Give time for async operations
|
||||
try await Task.sleep(nanoseconds: 50_000_000)
|
||||
|
||||
|
||||
try await Task.sleep(for: .milliseconds(50))
|
||||
|
||||
XCTAssertNotNil(timerEngine.activeReminder)
|
||||
}
|
||||
|
||||
|
||||
func testDismissReminder() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.triggerReminder(for: firstTimer)
|
||||
XCTAssertNotNil(timerEngine.activeReminder)
|
||||
|
||||
|
||||
timerEngine.dismissReminder()
|
||||
XCTAssertNil(timerEngine.activeReminder)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Time Remaining Tests
|
||||
|
||||
|
||||
func testGetTimeRemaining() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let remaining = timerEngine.getTimeRemaining(for: firstTimer)
|
||||
XCTAssertGreaterThan(remaining, 0)
|
||||
}
|
||||
|
||||
|
||||
func testGetFormattedTimeRemaining() {
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let formatted = timerEngine.getFormattedTimeRemaining(for: firstTimer)
|
||||
XCTAssertFalse(formatted.isEmpty)
|
||||
XCTAssertTrue(formatted.contains(":"))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Timer State Publisher Tests
|
||||
|
||||
|
||||
func testTimerStatesPublisher() async throws {
|
||||
let expectation = XCTestExpectation(description: "Timer states changed")
|
||||
|
||||
|
||||
timerEngine.$timerStates
|
||||
.dropFirst()
|
||||
.sink { states in
|
||||
@@ -220,15 +220,15 @@ final class TimerEngineTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1.0)
|
||||
}
|
||||
|
||||
|
||||
func testActiveReminderPublisher() async throws {
|
||||
let expectation = XCTestExpectation(description: "Active reminder changed")
|
||||
|
||||
|
||||
timerEngine.$activeReminder
|
||||
.dropFirst()
|
||||
.sink { reminder in
|
||||
@@ -237,67 +237,67 @@ final class TimerEngineTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
guard let firstTimer = timerEngine.timerStates.keys.first else {
|
||||
XCTFail("No timers available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
timerEngine.triggerReminder(for: firstTimer)
|
||||
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1.0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - System Sleep/Wake Tests
|
||||
|
||||
|
||||
func testHandleSystemSleep() {
|
||||
timerEngine.start()
|
||||
let statesBefore = timerEngine.timerStates.count
|
||||
|
||||
|
||||
timerEngine.handleSystemSleep()
|
||||
|
||||
|
||||
// States should still exist
|
||||
XCTAssertEqual(timerEngine.timerStates.count, statesBefore)
|
||||
}
|
||||
|
||||
|
||||
func testHandleSystemWake() {
|
||||
timerEngine.start()
|
||||
timerEngine.handleSystemSleep()
|
||||
timerEngine.handleSystemWake()
|
||||
|
||||
|
||||
// Should handle wake event without crashing
|
||||
XCTAssertGreaterThan(timerEngine.timerStates.count, 0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Disabled Timer Tests
|
||||
|
||||
|
||||
func testDisabledTimersNotInitialized() {
|
||||
var settings = AppSettings.defaults
|
||||
settings.lookAwayTimer.enabled = false
|
||||
settings.blinkTimer.enabled = false
|
||||
settings.postureTimer.enabled = false
|
||||
|
||||
|
||||
let settingsManager = EnhancedMockSettingsManager(settings: settings)
|
||||
let engine = TimerEngine(settingsManager: settingsManager)
|
||||
|
||||
|
||||
engine.start()
|
||||
|
||||
|
||||
XCTAssertEqual(engine.timerStates.count, 0)
|
||||
}
|
||||
|
||||
|
||||
func testPartiallyEnabledTimers() {
|
||||
var settings = AppSettings.defaults
|
||||
settings.lookAwayTimer.enabled = true
|
||||
settings.blinkTimer.enabled = false
|
||||
settings.postureTimer.enabled = false
|
||||
|
||||
|
||||
let settingsManager = EnhancedMockSettingsManager(settings: settings)
|
||||
let engine = TimerEngine(settingsManager: settingsManager)
|
||||
|
||||
|
||||
engine.start()
|
||||
|
||||
|
||||
XCTAssertEqual(engine.timerStates.count, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
// Test helpers and utilities for unit testing.
|
||||
//
|
||||
|
||||
// MARK: - Import Statement for Combine
|
||||
import Combine
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import Gaze
|
||||
|
||||
// MARK: - Enhanced MockSettingsManager
|
||||
@@ -16,21 +19,22 @@ import XCTest
|
||||
@Observable
|
||||
final class EnhancedMockSettingsManager: SettingsProviding {
|
||||
var settings: AppSettings
|
||||
|
||||
|
||||
@ObservationIgnored
|
||||
private let _settingsSubject: CurrentValueSubject<AppSettings, Never>
|
||||
|
||||
|
||||
var settingsPublisher: AnyPublisher<AppSettings, Never> {
|
||||
_settingsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
@ObservationIgnored
|
||||
private let timerConfigKeyPaths: [TimerType: WritableKeyPath<AppSettings, TimerConfiguration>] = [
|
||||
.lookAway: \.lookAwayTimer,
|
||||
.blink: \.blinkTimer,
|
||||
.posture: \.postureTimer,
|
||||
]
|
||||
|
||||
private let timerConfigKeyPaths: [TimerType: WritableKeyPath<AppSettings, TimerConfiguration>] =
|
||||
[
|
||||
.lookAway: \.lookAwayTimer,
|
||||
.blink: \.blinkTimer,
|
||||
.posture: \.postureTimer,
|
||||
]
|
||||
|
||||
// Track method calls for verification
|
||||
@ObservationIgnored
|
||||
private(set) var saveCallCount = 0
|
||||
@@ -40,19 +44,19 @@ final class EnhancedMockSettingsManager: SettingsProviding {
|
||||
private(set) var loadCallCount = 0
|
||||
@ObservationIgnored
|
||||
private(set) var resetToDefaultsCallCount = 0
|
||||
|
||||
|
||||
init(settings: AppSettings = .defaults) {
|
||||
self.settings = settings
|
||||
self._settingsSubject = CurrentValueSubject(settings)
|
||||
}
|
||||
|
||||
|
||||
func timerConfiguration(for type: TimerType) -> TimerConfiguration {
|
||||
guard let keyPath = timerConfigKeyPaths[type] else {
|
||||
preconditionFailure("Unknown timer type: \(type)")
|
||||
}
|
||||
return settings[keyPath: keyPath]
|
||||
}
|
||||
|
||||
|
||||
func updateTimerConfiguration(for type: TimerType, configuration: TimerConfiguration) {
|
||||
guard let keyPath = timerConfigKeyPaths[type] else {
|
||||
preconditionFailure("Unknown timer type: \(type)")
|
||||
@@ -60,7 +64,7 @@ final class EnhancedMockSettingsManager: SettingsProviding {
|
||||
settings[keyPath: keyPath] = configuration
|
||||
_settingsSubject.send(settings)
|
||||
}
|
||||
|
||||
|
||||
func allTimerConfigurations() -> [TimerType: TimerConfiguration] {
|
||||
var configs: [TimerType: TimerConfiguration] = [:]
|
||||
for (type, keyPath) in timerConfigKeyPaths {
|
||||
@@ -68,27 +72,27 @@ final class EnhancedMockSettingsManager: SettingsProviding {
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
|
||||
func save() {
|
||||
saveCallCount += 1
|
||||
_settingsSubject.send(settings)
|
||||
}
|
||||
|
||||
|
||||
func saveImmediately() {
|
||||
saveImmediatelyCallCount += 1
|
||||
_settingsSubject.send(settings)
|
||||
}
|
||||
|
||||
|
||||
func load() {
|
||||
loadCallCount += 1
|
||||
}
|
||||
|
||||
|
||||
func resetToDefaults() {
|
||||
resetToDefaultsCallCount += 1
|
||||
settings = .defaults
|
||||
_settingsSubject.send(settings)
|
||||
}
|
||||
|
||||
|
||||
// Test helpers
|
||||
func reset() {
|
||||
saveCallCount = 0
|
||||
@@ -105,17 +109,17 @@ final class EnhancedMockSettingsManager: SettingsProviding {
|
||||
@MainActor
|
||||
final class MockFullscreenDetectionService: ObservableObject, FullscreenDetectionProviding {
|
||||
@Published var isFullscreenActive: Bool = false
|
||||
|
||||
|
||||
var isFullscreenActivePublisher: Published<Bool>.Publisher {
|
||||
$isFullscreenActive
|
||||
}
|
||||
|
||||
|
||||
private(set) var forceUpdateCallCount = 0
|
||||
|
||||
|
||||
func forceUpdate() {
|
||||
forceUpdateCallCount += 1
|
||||
}
|
||||
|
||||
|
||||
func simulateFullscreen(_ active: Bool) {
|
||||
isFullscreenActive = active
|
||||
}
|
||||
@@ -124,22 +128,22 @@ final class MockFullscreenDetectionService: ObservableObject, FullscreenDetectio
|
||||
@MainActor
|
||||
final class MockIdleMonitoringService: ObservableObject, IdleMonitoringProviding {
|
||||
@Published var isIdle: Bool = false
|
||||
|
||||
|
||||
var isIdlePublisher: Published<Bool>.Publisher {
|
||||
$isIdle
|
||||
}
|
||||
|
||||
|
||||
private(set) var thresholdMinutes: Int = 5
|
||||
private(set) var forceUpdateCallCount = 0
|
||||
|
||||
|
||||
func updateThreshold(minutes: Int) {
|
||||
thresholdMinutes = minutes
|
||||
}
|
||||
|
||||
|
||||
func forceUpdate() {
|
||||
forceUpdateCallCount += 1
|
||||
}
|
||||
|
||||
|
||||
func simulateIdle(_ idle: Bool) {
|
||||
isIdle = idle
|
||||
}
|
||||
@@ -156,7 +160,7 @@ extension AppSettings {
|
||||
settings.postureTimer.enabled = false
|
||||
return settings
|
||||
}
|
||||
|
||||
|
||||
/// Settings with only lookAway timer enabled
|
||||
static var onlyLookAwayEnabled: AppSettings {
|
||||
var settings = AppSettings.defaults
|
||||
@@ -165,7 +169,7 @@ extension AppSettings {
|
||||
settings.postureTimer.enabled = false
|
||||
return settings
|
||||
}
|
||||
|
||||
|
||||
/// Settings with short intervals for testing
|
||||
static var shortIntervals: AppSettings {
|
||||
var settings = AppSettings.defaults
|
||||
@@ -174,14 +178,14 @@ extension AppSettings {
|
||||
settings.postureTimer.intervalSeconds = 7
|
||||
return settings
|
||||
}
|
||||
|
||||
|
||||
/// Settings with onboarding completed
|
||||
static var onboardingCompleted: AppSettings {
|
||||
var settings = AppSettings.defaults
|
||||
settings.hasCompletedOnboarding = true
|
||||
return settings
|
||||
}
|
||||
|
||||
|
||||
/// Settings with smart mode fully enabled
|
||||
static var smartModeEnabled: AppSettings {
|
||||
var settings = AppSettings.defaults
|
||||
@@ -209,19 +213,19 @@ struct TestEnvironment {
|
||||
let windowManager: MockWindowManager
|
||||
let settingsManager: EnhancedMockSettingsManager
|
||||
let timeProvider: MockTimeProvider
|
||||
|
||||
|
||||
init(settings: AppSettings = .defaults) {
|
||||
self.settingsManager = EnhancedMockSettingsManager(settings: settings)
|
||||
self.container = ServiceContainer(settingsManager: settingsManager)
|
||||
self.windowManager = MockWindowManager()
|
||||
self.timeProvider = MockTimeProvider()
|
||||
}
|
||||
|
||||
|
||||
/// Creates an AppDelegate with all test dependencies
|
||||
func createAppDelegate() -> AppDelegate {
|
||||
return AppDelegate(serviceContainer: container, windowManager: windowManager)
|
||||
}
|
||||
|
||||
|
||||
/// Resets all mock state
|
||||
func reset() {
|
||||
windowManager.reset()
|
||||
@@ -245,10 +249,10 @@ extension XCTestCase {
|
||||
XCTFail(message)
|
||||
return
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 10_000_000) // 10ms
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Waits for a published value to change
|
||||
@MainActor
|
||||
func waitForPublisher<T: Equatable>(
|
||||
@@ -258,17 +262,14 @@ extension XCTestCase {
|
||||
) async throws {
|
||||
let expectation = XCTestExpectation(description: "Publisher value changed")
|
||||
var cancellable: AnyCancellable?
|
||||
|
||||
|
||||
cancellable = publisher.sink { value in
|
||||
if value == expectedValue {
|
||||
expectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await fulfillment(of: [expectation], timeout: timeout)
|
||||
cancellable?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Import Statement for Combine
|
||||
import Combine
|
||||
|
||||
@@ -7,24 +7,25 @@
|
||||
|
||||
import Combine
|
||||
import XCTest
|
||||
|
||||
@testable import Gaze
|
||||
|
||||
@MainActor
|
||||
final class TimerEngineTestabilityTests: XCTestCase {
|
||||
|
||||
|
||||
var testEnv: TestEnvironment!
|
||||
var cancellables: Set<AnyCancellable>!
|
||||
|
||||
|
||||
override func setUp() async throws {
|
||||
testEnv = TestEnvironment(settings: .shortIntervals)
|
||||
cancellables = []
|
||||
}
|
||||
|
||||
|
||||
override func tearDown() async throws {
|
||||
cancellables = nil
|
||||
testEnv = nil
|
||||
}
|
||||
|
||||
|
||||
func testTimerEngineCreationWithMocks() {
|
||||
let timeProvider = MockTimeProvider()
|
||||
let timerEngine = TimerEngine(
|
||||
@@ -32,30 +33,30 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
|
||||
XCTAssertNotNil(timerEngine)
|
||||
XCTAssertEqual(timerEngine.timerStates.count, 0)
|
||||
}
|
||||
|
||||
|
||||
func testTimerEngineUsesInjectedSettings() {
|
||||
var settings = AppSettings.defaults
|
||||
settings.lookAwayTimer.enabled = true
|
||||
settings.blinkTimer.enabled = false
|
||||
settings.postureTimer.enabled = false
|
||||
|
||||
|
||||
testEnv.settingsManager.settings = settings
|
||||
let timerEngine = testEnv.container.timerEngine
|
||||
|
||||
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
// Only lookAway should be active
|
||||
let lookAwayTimer = timerEngine.timerStates.first { $0.key == .builtIn(.lookAway) }
|
||||
let blinkTimer = timerEngine.timerStates.first { $0.key == .builtIn(.blink) }
|
||||
|
||||
|
||||
XCTAssertNotNil(lookAwayTimer)
|
||||
XCTAssertNil(blinkTimer)
|
||||
}
|
||||
|
||||
|
||||
func testTimerEngineWithMockTimeProvider() {
|
||||
let timeProvider = MockTimeProvider(startTime: Date())
|
||||
let timerEngine = TimerEngine(
|
||||
@@ -63,52 +64,51 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
|
||||
// Start timers
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
// Advance time
|
||||
timeProvider.advance(by: 10)
|
||||
|
||||
|
||||
// Timer engine should use the mocked time
|
||||
XCTAssertNotNil(timerEngine.timerStates)
|
||||
}
|
||||
|
||||
|
||||
func testPauseAndResumeWithMocks() {
|
||||
let timerEngine = testEnv.container.timerEngine
|
||||
timerEngine.start()
|
||||
|
||||
|
||||
timerEngine.pause()
|
||||
|
||||
|
||||
// Verify all timers are paused
|
||||
for (_, state) in timerEngine.timerStates {
|
||||
XCTAssertTrue(state.isPaused)
|
||||
}
|
||||
|
||||
|
||||
timerEngine.resume()
|
||||
|
||||
|
||||
// Verify all timers are resumed
|
||||
for (_, state) in timerEngine.timerStates {
|
||||
XCTAssertFalse(state.isPaused)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testReminderEventPublishing() async throws {
|
||||
let timerEngine = testEnv.container.timerEngine
|
||||
|
||||
|
||||
var receivedReminder: ReminderEvent?
|
||||
timerEngine.$activeReminder
|
||||
.sink { reminder in
|
||||
receivedReminder = reminder
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
let timerId = TimerIdentifier.builtIn(.lookAway)
|
||||
timerEngine.triggerReminder(for: timerId)
|
||||
|
||||
// Give time for publisher to fire
|
||||
try await Task.sleep(nanoseconds: 10_000_000)
|
||||
|
||||
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
|
||||
XCTAssertNotNil(receivedReminder)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user