// // StreakTests.swift // ReflectTests // // Tests for DataController streak calculation. // import XCTest import SwiftData @testable import Reflect @MainActor final class StreakTests: XCTestCase { var sut: DataController! override func setUp() { super.setUp() let schema = Schema([MoodEntryModel.self]) let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) sut = DataController(container: try! ModelContainer(for: schema, configurations: [config])) } override func tearDown() { sut = nil super.tearDown() } // MARK: - Phase 6: Streak Calculation func test_streak_emptyDB_zero() { let result = sut.calculateStreak(from: Date()) XCTAssertEqual(result.streak, 0) XCTAssertNil(result.todaysMood) } func test_streak_singleEntryToday_one() { let today = Calendar.current.startOfDay(for: Date()) sut.add(mood: .great, forDate: today, entryType: .listView) let result = sut.calculateStreak(from: today) XCTAssertEqual(result.streak, 1) XCTAssertEqual(result.todaysMood, .great) } func test_streak_consecutiveDays() { let today = Calendar.current.startOfDay(for: Date()) for i in 0..<5 { let date = Calendar.current.date(byAdding: .day, value: -i, to: today)! sut.add(mood: .good, forDate: date, entryType: .listView) } let result = sut.calculateStreak(from: today) XCTAssertEqual(result.streak, 5) } func test_streak_gapBreaks() { let today = Calendar.current.startOfDay(for: Date()) // Today, yesterday, then skip a day, then 2 more sut.add(mood: .good, forDate: today, entryType: .listView) sut.add(mood: .good, forDate: Calendar.current.date(byAdding: .day, value: -1, to: today)!, entryType: .listView) // Skip day -2 sut.add(mood: .good, forDate: Calendar.current.date(byAdding: .day, value: -3, to: today)!, entryType: .listView) sut.add(mood: .good, forDate: Calendar.current.date(byAdding: .day, value: -4, to: today)!, entryType: .listView) let result = sut.calculateStreak(from: today) XCTAssertEqual(result.streak, 2, "Streak should stop at the gap") } func test_streak_missingDoesNotCount() { let today = Calendar.current.startOfDay(for: Date()) sut.add(mood: .good, forDate: today, entryType: .listView) // Yesterday is a .missing entry (should be ignored) sut.add(mood: .missing, forDate: Calendar.current.date(byAdding: .day, value: -1, to: today)!, entryType: .filledInMissing) sut.add(mood: .good, forDate: Calendar.current.date(byAdding: .day, value: -2, to: today)!, entryType: .listView) let result = sut.calculateStreak(from: today) XCTAssertEqual(result.streak, 1, ".missing entries should not count toward streak") } func test_streak_noEntryToday_countsFromYesterday() { let today = Calendar.current.startOfDay(for: Date()) let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)! let twoDaysAgo = Calendar.current.date(byAdding: .day, value: -2, to: today)! sut.add(mood: .good, forDate: yesterday, entryType: .listView) sut.add(mood: .great, forDate: twoDaysAgo, entryType: .listView) let result = sut.calculateStreak(from: today) XCTAssertEqual(result.streak, 2, "Should count from yesterday when today has no entry") XCTAssertNil(result.todaysMood, "Today's mood should be nil when no entry exists") } func test_streak_entryAfterVotingTime_stillCounts() { // Bug: calculateStreak passes votingDate (with time) as endDate to getData. // getData uses <=, so an entry logged AFTER votingDate's time is excluded. // E.g. if votingDate is "today at 8am" and user logged at 10am, the 10am entry // is excluded from the query, making streak miss today. let today = Calendar.current.startOfDay(for: Date()) let morningVotingDate = Calendar.current.date(byAdding: .hour, value: 8, to: today)! // Add entry at 10am (after the 8am voting date time) let afternoonDate = Calendar.current.date(byAdding: .hour, value: 10, to: today)! sut.add(mood: .great, forDate: afternoonDate, entryType: .listView) let result = sut.calculateStreak(from: morningVotingDate) XCTAssertEqual(result.streak, 1, "Entry logged after votingDate time should still count") XCTAssertEqual(result.todaysMood, .great, "Today's mood should be found even if logged after votingDate time") } func test_streak_afterDelete_decreases() { let today = Calendar.current.startOfDay(for: Date()) let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)! sut.add(mood: .good, forDate: today, entryType: .listView) sut.add(mood: .great, forDate: yesterday, entryType: .listView) let beforeDelete = sut.calculateStreak(from: today) XCTAssertEqual(beforeDelete.streak, 2) sut.deleteAllEntries(forDate: yesterday) let afterDelete = sut.calculateStreak(from: today) XCTAssertEqual(afterDelete.streak, 1, "Streak should decrease after deleting a day") } }