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>
This commit is contained in:
125
ReflectTests/StreakTests.swift
Normal file
125
ReflectTests/StreakTests.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user