feat: persistence

This commit is contained in:
Michael Freno
2026-01-13 10:16:33 -05:00
parent f74390e4c3
commit 338e79c6c6
7 changed files with 363 additions and 61 deletions

View File

@@ -31,7 +31,7 @@ final class IntegrationTests: XCTestCase {
func testSettingsChangePropagateToTimerEngine() {
timerEngine.start()
let originalInterval = timerEngine.timerStates[.lookAway]?.remainingSeconds
let originalInterval = timerEngine.timerStates[.builtIn(.lookAway)]?.remainingSeconds
XCTAssertEqual(originalInterval, 20 * 60)
let newConfig = TimerConfiguration(enabled: true, intervalSeconds: 10 * 60)
@@ -39,31 +39,31 @@ final class IntegrationTests: XCTestCase {
timerEngine.start()
let newInterval = timerEngine.timerStates[.lookAway]?.remainingSeconds
let newInterval = timerEngine.timerStates[.builtIn(.lookAway)]?.remainingSeconds
XCTAssertEqual(newInterval, 10 * 60)
}
func testDisablingTimerRemovesFromEngine() {
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.blink])
XCTAssertNotNil(timerEngine.timerStates[.builtIn(.blink)])
var config = TimerConfiguration(enabled: false, intervalSeconds: 5 * 60)
settingsManager.updateTimerConfiguration(for: .blink, configuration: config)
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.blink])
XCTAssertNil(timerEngine.timerStates[.builtIn(.blink)])
}
func testEnablingTimerAddsToEngine() {
settingsManager.settings.postureTimer.enabled = false
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.posture])
XCTAssertNil(timerEngine.timerStates[.builtIn(.posture)])
let config = TimerConfiguration(enabled: true, intervalSeconds: 30 * 60)
settingsManager.updateTimerConfiguration(for: .posture, configuration: config)
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.posture])
XCTAssertNotNil(timerEngine.timerStates[.builtIn(.posture)])
}
func testSettingsPersistAcrossEngineLifecycle() {
@@ -76,7 +76,7 @@ final class IntegrationTests: XCTestCase {
let newEngine = TimerEngine(settingsManager: settingsManager)
newEngine.start()
XCTAssertNil(newEngine.timerStates[.lookAway])
XCTAssertNil(newEngine.timerStates[.builtIn(.lookAway)])
}
func testMultipleTimerConfigurationUpdates() {
@@ -94,9 +94,9 @@ final class IntegrationTests: XCTestCase {
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates[.lookAway]?.remainingSeconds, 600)
XCTAssertEqual(timerEngine.timerStates[.blink]?.remainingSeconds, 300)
XCTAssertEqual(timerEngine.timerStates[.posture]?.remainingSeconds, 1800)
XCTAssertEqual(timerEngine.timerStates[.builtIn(.lookAway)]?.remainingSeconds, 600)
XCTAssertEqual(timerEngine.timerStates[.builtIn(.blink)]?.remainingSeconds, 300)
XCTAssertEqual(timerEngine.timerStates[.builtIn(.posture)]?.remainingSeconds, 1800)
}
func testResetToDefaultsAffectsTimerEngine() {
@@ -104,13 +104,13 @@ final class IntegrationTests: XCTestCase {
settingsManager.updateTimerConfiguration(for: .blink, configuration: config)
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.blink])
XCTAssertNil(timerEngine.timerStates[.builtIn(.blink)])
settingsManager.resetToDefaults()
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.blink])
XCTAssertEqual(timerEngine.timerStates[.blink]?.remainingSeconds, 5 * 60)
XCTAssertNotNil(timerEngine.timerStates[.builtIn(.blink)])
XCTAssertEqual(timerEngine.timerStates[.builtIn(.blink)]?.remainingSeconds, 5 * 60)
}
func testTimerEngineRespectsDisabledTimers() {
@@ -138,8 +138,8 @@ final class IntegrationTests: XCTestCase {
XCTAssertFalse(state.isPaused)
}
timerEngine.skipNext(type: .lookAway)
XCTAssertEqual(timerEngine.timerStates[.lookAway]?.remainingSeconds, 20 * 60)
timerEngine.skipNext(identifier: .builtIn(.lookAway))
XCTAssertEqual(timerEngine.timerStates[.builtIn(.lookAway)]?.remainingSeconds, 20 * 60)
timerEngine.stop()
XCTAssertTrue(timerEngine.timerStates.isEmpty)
@@ -148,7 +148,7 @@ final class IntegrationTests: XCTestCase {
func testReminderWorkflow() {
timerEngine.start()
timerEngine.triggerReminder(for: .lookAway)
timerEngine.triggerReminder(for: .builtIn(.lookAway))
XCTAssertNotNil(timerEngine.activeReminder)
for (_, state) in timerEngine.timerStates {

View File

@@ -40,19 +40,19 @@ final class ReminderEventTests: XCTestCase {
}
}
func testTypePropertyForLookAway() {
func testIdentifierPropertyForLookAway() {
let event = ReminderEvent.lookAwayTriggered(countdownSeconds: 20)
XCTAssertEqual(event.type, .lookAway)
XCTAssertEqual(event.identifier, .builtIn(.lookAway))
}
func testTypePropertyForBlink() {
func testIdentifierPropertyForBlink() {
let event = ReminderEvent.blinkTriggered
XCTAssertEqual(event.type, .blink)
XCTAssertEqual(event.identifier, .builtIn(.blink))
}
func testTypePropertyForPosture() {
func testIdentifierPropertyForPosture() {
let event = ReminderEvent.postureTriggered
XCTAssertEqual(event.type, .posture)
XCTAssertEqual(event.identifier, .builtIn(.posture))
}
func testEquality() {
@@ -79,9 +79,9 @@ final class ReminderEventTests: XCTestCase {
XCTAssertNotEqual(event2, event3)
XCTAssertNotEqual(event1, event3)
XCTAssertEqual(event1.type, .lookAway)
XCTAssertEqual(event2.type, .lookAway)
XCTAssertEqual(event3.type, .lookAway)
XCTAssertEqual(event1.identifier, .builtIn(.lookAway))
XCTAssertEqual(event2.identifier, .builtIn(.lookAway))
XCTAssertEqual(event3.identifier, .builtIn(.lookAway))
}
func testNegativeCountdown() {
@@ -104,11 +104,13 @@ final class ReminderEventTests: XCTestCase {
for event in events {
switch event {
case .lookAwayTriggered:
XCTAssertEqual(event.type, .lookAway)
XCTAssertEqual(event.identifier, .builtIn(.lookAway))
case .blinkTriggered:
XCTAssertEqual(event.type, .blink)
XCTAssertEqual(event.identifier, .builtIn(.blink))
case .postureTriggered:
XCTAssertEqual(event.type, .posture)
XCTAssertEqual(event.identifier, .builtIn(.posture))
case .userTimerTriggered:
XCTFail("Unexpected user timer in this test")
}
}
}

View File

@@ -11,34 +11,34 @@ import XCTest
final class TimerStateTests: XCTestCase {
func testInitialization() {
let state = TimerState(type: .lookAway, intervalSeconds: 1200)
let state = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200)
XCTAssertEqual(state.type, .lookAway)
XCTAssertEqual(state.identifier, .builtIn(.lookAway))
XCTAssertEqual(state.remainingSeconds, 1200)
XCTAssertFalse(state.isPaused)
XCTAssertTrue(state.isActive)
}
func testInitializationWithPausedState() {
let state = TimerState(type: .blink, intervalSeconds: 300, isPaused: true)
let state = TimerState(identifier: .builtIn(.blink), intervalSeconds: 300, isPaused: true)
XCTAssertEqual(state.type, .blink)
XCTAssertEqual(state.identifier, .builtIn(.blink))
XCTAssertEqual(state.remainingSeconds, 300)
XCTAssertTrue(state.isPaused)
XCTAssertTrue(state.isActive)
}
func testInitializationWithInactiveState() {
let state = TimerState(type: .posture, intervalSeconds: 1800, isPaused: false, isActive: false)
let state = TimerState(identifier: .builtIn(.posture), intervalSeconds: 1800, isPaused: false, isActive: false)
XCTAssertEqual(state.type, .posture)
XCTAssertEqual(state.identifier, .builtIn(.posture))
XCTAssertEqual(state.remainingSeconds, 1800)
XCTAssertFalse(state.isPaused)
XCTAssertFalse(state.isActive)
}
func testMutability() {
var state = TimerState(type: .lookAway, intervalSeconds: 1200)
var state = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200)
state.remainingSeconds = 600
XCTAssertEqual(state.remainingSeconds, 600)
@@ -51,10 +51,10 @@ final class TimerStateTests: XCTestCase {
}
func testEquality() {
let state1 = TimerState(type: .lookAway, intervalSeconds: 1200)
let state2 = TimerState(type: .lookAway, intervalSeconds: 1200)
let state3 = TimerState(type: .blink, intervalSeconds: 1200)
let state4 = TimerState(type: .lookAway, intervalSeconds: 600)
let state1 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200)
let state2 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200)
let state3 = TimerState(identifier: .builtIn(.blink), intervalSeconds: 1200)
let state4 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 600)
XCTAssertEqual(state1, state2)
XCTAssertNotEqual(state1, state3)
@@ -62,32 +62,32 @@ final class TimerStateTests: XCTestCase {
}
func testEqualityWithDifferentPausedState() {
let state1 = TimerState(type: .lookAway, intervalSeconds: 1200, isPaused: false)
let state2 = TimerState(type: .lookAway, intervalSeconds: 1200, isPaused: true)
let state1 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200, isPaused: false)
let state2 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200, isPaused: true)
XCTAssertNotEqual(state1, state2)
}
func testEqualityWithDifferentActiveState() {
let state1 = TimerState(type: .lookAway, intervalSeconds: 1200, isActive: true)
let state2 = TimerState(type: .lookAway, intervalSeconds: 1200, isActive: false)
let state1 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200, isActive: true)
let state2 = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 1200, isActive: false)
XCTAssertNotEqual(state1, state2)
}
func testZeroRemainingSeconds() {
let state = TimerState(type: .lookAway, intervalSeconds: 0)
let state = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 0)
XCTAssertEqual(state.remainingSeconds, 0)
}
func testNegativeRemainingSeconds() {
var state = TimerState(type: .lookAway, intervalSeconds: 10)
var state = TimerState(identifier: .builtIn(.lookAway), intervalSeconds: 10)
state.remainingSeconds = -5
XCTAssertEqual(state.remainingSeconds, -5)
}
func testLargeIntervalSeconds() {
let state = TimerState(type: .posture, intervalSeconds: 86400)
let state = TimerState(identifier: .builtIn(.posture), intervalSeconds: 86400)
XCTAssertEqual(state.remainingSeconds, 86400)
}
}