Files
Reflect/ReflectTests/StreakTests.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

126 lines
5.2 KiB
Swift

//
// 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")
}
}