Andromida/AndromidaTests/RitualStoreTests.swift

215 lines
7.3 KiB
Swift

import Foundation
import SwiftData
import Testing
@testable import Rituals
struct RitualStoreTests {
@MainActor
@Test func quickRitualStartsIncomplete() throws {
let store = makeStore()
store.createQuickRitual()
#expect(store.activeRitual != nil)
#expect(abs(store.activeRitualProgress) < 0.0001)
}
@MainActor
@Test func toggleHabitCompletionMarksComplete() throws {
let store = makeStore()
store.createQuickRitual()
guard let habit = store.activeRitual?.habits.first else {
throw TestError.missingHabit
}
store.toggleHabitCompletion(habit)
#expect(store.isHabitCompletedToday(habit) == true)
}
@MainActor
@Test func toggleHabitCompletionForSpecificDate() throws {
let store = makeStore()
store.createQuickRitual()
guard let habit = store.activeRitual?.habits.first else {
throw TestError.missingHabit
}
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
store.toggleHabitCompletion(habit, date: yesterday)
let completions = store.habitCompletions(for: yesterday)
let completion = completions.first { $0.habit.id == habit.id }
#expect(completion?.isCompleted == true)
#expect(store.isHabitCompletedToday(habit) == false)
}
@MainActor
@Test func arcRenewalCreatesNewArc() throws {
let store = makeStore()
store.createQuickRitual()
guard let ritual = store.activeRitual else {
throw TestError.missingHabit
}
#expect((ritual.arcs?.count ?? 0) == 1)
#expect(ritual.currentArc?.arcNumber == 1)
// End the current arc
store.endArc(for: ritual)
#expect(ritual.currentArc == nil)
// Renew the arc
store.renewArc(for: ritual, durationDays: 30, copyHabits: true)
#expect((ritual.arcs?.count ?? 0) == 2)
#expect(ritual.currentArc?.arcNumber == 2)
#expect((ritual.currentArc?.habits?.count ?? 0) == 3)
}
@MainActor
@Test func currentStreakCountsOnlyPerfectDays() throws {
let store = makeStore()
store.createQuickRitual()
guard let ritual = store.activeRitual else {
throw TestError.missingRitual
}
let habits = ritual.habits
guard habits.count >= 2 else {
throw TestError.missingHabit
}
let calendar = Calendar.current
let today = Date()
let yesterday = calendar.date(byAdding: .day, value: -1, to: today) ?? today
// Yesterday is perfect: complete all habits.
for habit in habits {
store.toggleHabitCompletion(habit, date: yesterday)
}
// Today is partial: complete only one habit.
store.toggleHabitCompletion(habits[0], date: today)
#expect(store.currentStreak() == 1)
}
@MainActor
@Test func allRitualCompletionRateIncludesEndedArcsForHistoricalDates() throws {
let store = makeStore()
store.createQuickRitual()
guard let ritual = store.activeRitual,
let habit = ritual.habits.first,
let arcStart = ritual.currentArc?.startDate else {
throw TestError.missingHabit
}
store.toggleHabitCompletion(habit, date: arcStart)
store.endArc(for: ritual)
let rate = store.completionRate(for: arcStart, ritual: nil)
#expect(rate > 0.0)
}
@MainActor
@Test func completedActiveArcStillTriggersRenewalPrompt() throws {
let store = makeStore()
store.createQuickRitual()
guard let ritual = store.activeRitual,
let arc = ritual.currentArc else {
throw TestError.missingRitual
}
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()
arc.endDate = yesterday
#expect(ritual.hasActiveArc == false)
store.checkForCompletedArcs()
#expect(store.ritualNeedingRenewal?.id == ritual.id)
}
@MainActor
@Test func currentArcUsesMostRecentActiveArc() throws {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let oldStart = calendar.date(byAdding: .day, value: -20, to: today) ?? today
let oldEnd = calendar.date(byAdding: .day, value: -5, to: today) ?? today
let currentStart = calendar.date(byAdding: .day, value: -2, to: today) ?? today
let currentEnd = calendar.date(byAdding: .day, value: 10, to: today) ?? today
let oldArc = RitualArc(startDate: oldStart, endDate: oldEnd, arcNumber: 1, isActive: true)
let currentArc = RitualArc(startDate: currentStart, endDate: currentEnd, arcNumber: 2, isActive: true)
let ritual = Ritual(title: "Test", theme: "Theme", arcs: [oldArc, currentArc])
#expect(ritual.currentArc?.arcNumber == 2)
#expect(ritual.hasActiveArc == true)
}
@MainActor
@Test func completedArcCountIncludesExpiredActiveArcs() throws {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let start = calendar.date(byAdding: .day, value: -10, to: today) ?? today
let end = calendar.date(byAdding: .day, value: -1, to: today) ?? today
let expiredButActiveArc = RitualArc(startDate: start, endDate: end, arcNumber: 1, isActive: true)
let ritual = Ritual(title: "Expired", theme: "Theme", arcs: [expiredButActiveArc])
#expect(ritual.completedArcCount == 1)
#expect(Calendar.current.isDate(ritual.lastCompletedDate ?? .distantPast, inSameDayAs: end))
}
@MainActor
@Test func nextUpcomingRitualContextIdentifiesTomorrowForFutureArc() throws {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let tomorrow = calendar.date(byAdding: .day, value: 1, to: today) ?? today
let nextWeek = calendar.date(byAdding: .day, value: 7, to: tomorrow) ?? tomorrow
let middayToday = calendar.date(byAdding: .hour, value: 12, to: today) ?? today
let tomorrowArc = RitualArc(startDate: tomorrow, endDate: nextWeek, arcNumber: 1, isActive: true)
let eveningRitual = Ritual(
title: "Evening Wind Down",
theme: "Test",
timeOfDay: .evening,
arcs: [tomorrowArc]
)
let context = RitualAnalytics.nextUpcomingRitualContext(from: [eveningRitual], currentDate: middayToday)
#expect(context?.ritual.id == eveningRitual.id)
#expect(context?.isTomorrow == true)
}
}
@MainActor
private func makeStore() -> RitualStore {
let schema = Schema([Ritual.self, RitualArc.self, ArcHabit.self])
let configuration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
let container: ModelContainer
do {
container = try ModelContainer(for: schema, configurations: [configuration])
} catch {
fatalError("Test container failed: \(error)")
}
return RitualStore(modelContext: container.mainContext, seedService: EmptySeedService(), settingsStore: SettingsStore())
}
private struct EmptySeedService: RitualSeedProviding {
func makeSeedRituals(startDate: Date) -> [Ritual] {
[]
}
}
private enum TestError: Error {
case missingHabit
case missingRitual
}