Files
InvestmentTrackerApp/PortfolioJournalTests/Utilities/MonthlyCheckInStoreTests.swift
T
alexandrev-tibco 167b736aad Fix streak: el mes en curso pendiente ya no rompe la racha (build 88) #47
La tarjeta Momentum & Streaks mostraba "Streak 0x" casi todo el mes
aunque la racha estuviera intacta: stats() empezaba a contar en el mes
en curso y cortaba en el primer mes sin check-in on-time. El check-in
del mes en curso está dentro de plazo hasta fin de mes (y con
graceDays = 20 se hace normalmente ya entrado el mes siguiente), así
que tenerlo pendiente no debe leerse como racha rota.

Ahora el recuento arranca en el mes en curso si ya está hecho y, si no,
en el anterior. Un mes anterior sin check-in sí rompe la racha, porque
su plazo ya venció.

Tests: 3 casos nuevos en MonthlyCheckInStoreTests (mes en curso
pendiente, mes en curso completado, mes anterior perdido).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YcDn5ccuRFV83q7xWWokBT
2026-09-12 16:19:46 +02:00

114 lines
5.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XCTest
@testable import PortfolioJournal
final class MonthlyCheckInStoreTests: XCTestCase {
override func setUp() {
super.setUp()
MonthlyCheckInStore.clearAll()
}
override func tearDown() {
MonthlyCheckInStore.clearAll()
super.tearDown()
}
func testEffectiveMonthUsesPreviousMonthWithinGracePeriod() {
let calendar = Calendar(identifier: .gregorian)
let referenceDate = calendar.date(from: DateComponents(year: 2026, month: 2, day: 20))!
let effective = MonthlyCheckInStore.effectiveMonth(for: referenceDate, relativeTo: referenceDate, graceDays: 20)
let expected = calendar.date(from: DateComponents(year: 2026, month: 1, day: 1))!
XCTAssertEqual(effective, expected)
}
func testEffectiveMonthUsesCurrentMonthAfterGracePeriod() {
let calendar = Calendar(identifier: .gregorian)
let referenceDate = calendar.date(from: DateComponents(year: 2026, month: 2, day: 21))!
let effective = MonthlyCheckInStore.effectiveMonth(for: referenceDate, relativeTo: referenceDate, graceDays: 20)
let expected = calendar.date(from: DateComponents(year: 2026, month: 2, day: 1))!
XCTAssertEqual(effective, expected)
}
func testCompletionAfterGraceAutoFillsMissingMonths() {
let calendar = Calendar(identifier: .gregorian)
let decemberDate = calendar.date(from: DateComponents(year: 2025, month: 12, day: 15))!
let decemberCompletion = calendar.date(from: DateComponents(year: 2025, month: 12, day: 31))!
MonthlyCheckInStore.setNote("Carry", for: decemberDate)
MonthlyCheckInStore.setRating(4, for: decemberDate)
MonthlyCheckInStore.setMood(.balanced, for: decemberDate)
MonthlyCheckInStore.setCompletionDate(decemberCompletion, for: decemberDate)
let completionDate = calendar.date(from: DateComponents(year: 2026, month: 2, day: 21))!
MonthlyCheckInStore.setCompletionDate(completionDate, for: completionDate)
let januaryDate = calendar.date(from: DateComponents(year: 2026, month: 1, day: 5))!
let januaryEntry = MonthlyCheckInStore.entry(for: januaryDate)
XCTAssertEqual(januaryEntry?.note, "Carry")
XCTAssertEqual(januaryEntry?.rating, 4)
XCTAssertEqual(januaryEntry?.mood, .balanced)
XCTAssertNotNil(januaryEntry?.completionDate)
}
// MARK: - Current streak
/// Completing JuneAugust and looking at the card on 12 September (the
/// September check-in is still within its deadline) must show a live streak,
/// not 0x.
func testCurrentStreakCountsWhenCurrentMonthIsStillPending() {
let calendar = Calendar(identifier: .gregorian)
for month in 6...8 {
let monthDate = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
let completion = calendar.date(from: DateComponents(year: 2026, month: month, day: 28))!
MonthlyCheckInStore.setCompletionDate(completion, for: monthDate)
}
let referenceDate = calendar.date(from: DateComponents(year: 2026, month: 9, day: 12))!
let stats = MonthlyCheckInStore.stats(referenceDate: referenceDate)
XCTAssertEqual(stats.currentStreak, 3)
}
/// The current month being pending is forgiven; an earlier month whose
/// deadline already passed is not.
func testCurrentStreakBreaksWhenPreviousMonthWasMissed() {
let calendar = Calendar(identifier: .gregorian)
for month in 6...8 {
let monthDate = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
let completion = calendar.date(from: DateComponents(year: 2026, month: month, day: 28))!
MonthlyCheckInStore.setCompletionDate(completion, for: monthDate)
}
// September was never checked in and October is under way.
let referenceDate = calendar.date(from: DateComponents(year: 2026, month: 10, day: 12))!
let stats = MonthlyCheckInStore.stats(referenceDate: referenceDate)
XCTAssertEqual(stats.currentStreak, 0)
}
func testCurrentStreakCountsCurrentMonthWhenAlreadyCompleted() {
let calendar = Calendar(identifier: .gregorian)
for month in 7...9 {
let monthDate = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
let completion = calendar.date(from: DateComponents(year: 2026, month: month, day: 28))!
MonthlyCheckInStore.setCompletionDate(completion, for: monthDate)
}
let referenceDate = calendar.date(from: DateComponents(year: 2026, month: 9, day: 29))!
let stats = MonthlyCheckInStore.stats(referenceDate: referenceDate)
XCTAssertEqual(stats.currentStreak, 3)
}
func testSetCompletionDateForVeryOldMonthStillCompletesEntry() {
let calendar = Calendar(identifier: .gregorian)
let oldMonthDate = calendar.date(from: DateComponents(year: 2024, month: 1, day: 10))!
let completionDate = calendar.date(from: DateComponents(year: 2026, month: 2, day: 22))!
MonthlyCheckInStore.setCompletionDate(completionDate, for: oldMonthDate)
let entry = MonthlyCheckInStore.entry(for: oldMonthDate)
XCTAssertNotNil(entry?.completionDate)
}
}