Files
KisaniCal/KisaniCalTests/RecurrenceTests.swift
kutesir af2996876c Tests: recurring-task occurrence expansion (date mapping)
8 tests covering daily / weekly / every-weekday / monthly / yearly expansion,
repeat-until cutoff, time-of-day preservation on the mapped local day, and
non-recurring single-day placement. Uses Calendar.current so it holds in any
timezone. Closes the recurrence-coverage gap from the calendar fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:59:43 +03:00

101 lines
4.2 KiB
Swift

import XCTest
@testable import KisaniCal
/// Verifies recurring-task occurrence expansion maps to the correct real calendar
/// days (the calendar's event-date mapping for tasks). Uses Calendar.current the
/// same calendar the production code uses so assertions hold in any timezone.
@MainActor
final class RecurrenceTests: XCTestCase {
private let cal = Calendar.current
private func day(_ y: Int, _ m: Int, _ d: Int, h: Int = 0, min: Int = 0) -> Date {
cal.date(from: DateComponents(year: y, month: m, day: d, hour: h, minute: min))!
}
private func vm(with tasks: [TaskItem]) -> TaskViewModel {
let v = TaskViewModel()
v.tasks = tasks
return v
}
private func task(_ label: String, start: Date, hasTime: Bool = false, end: Date? = nil) -> TaskItem {
var t = TaskItem(title: "T-\(label)", dueDate: start)
t.isRecurring = true
t.recurrenceLabel = label
t.hasTime = hasTime
t.recurrenceEnd = end
return t
}
private func hasOccurrence(_ v: TaskViewModel, _ id: UUID, on date: Date) -> Bool {
v.occurrences(on: date).contains { $0.id == id }
}
func testDailyExpandsEveryDayFromStart() {
let t = task("Daily", start: day(2026, 6, 1))
let v = vm(with: [t])
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 5, 31)), "no occurrence before start")
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 1)))
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 24)))
}
func testWeeklyHitsEverySevenDaysOnly() {
let t = task("Weekly", start: day(2026, 6, 1)) // Monday
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 8)))
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 22)))
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 9)), "off-cadence day must not match")
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 24)))
}
func testEveryWeekdaySkipsWeekend() {
let t = task("Every Weekday", start: day(2026, 6, 1))
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 24)), "Wed is a weekday")
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 27)), "Sat excluded")
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 28)), "Sun excluded")
}
func testMonthlyMatchesDayOfMonth() {
let t = task("Monthly", start: day(2026, 1, 15))
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 15)))
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 16)))
}
func testYearlyMatchesMonthAndDay() {
let t = task("Yearly", start: day(2025, 6, 24))
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 24)))
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 23)))
}
func testRecurrenceEndStopsExpansion() {
let t = task("Daily", start: day(2026, 6, 1), end: day(2026, 6, 10))
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 10)), "inclusive of end day")
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 11)), "past repeat-until")
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 24)))
}
func testOccurrenceCopyPreservesTimeOnTargetLocalDay() {
let t = task("Daily", start: day(2026, 6, 1, h: 9, min: 30), hasTime: true)
let v = vm(with: [t])
let occ = v.occurrences(on: day(2026, 6, 24)).first { $0.id == t.id }
XCTAssertNotNil(occ)
XCTAssertTrue(cal.isDate(occ!.dueDate!, inSameDayAs: day(2026, 6, 24)), "mapped to June 24")
XCTAssertEqual(cal.component(.hour, from: occ!.dueDate!), 9)
XCTAssertEqual(cal.component(.minute, from: occ!.dueDate!), 30)
}
func testNonRecurringTaskAppearsOnlyOnItsDay() {
var t = TaskItem(title: "Dinner with Jay", dueDate: day(2026, 6, 24))
t.isRecurring = false
let v = vm(with: [t])
XCTAssertTrue(hasOccurrence(v, t.id, on: day(2026, 6, 24)))
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 23)))
XCTAssertFalse(hasOccurrence(v, t.id, on: day(2026, 6, 25)))
}
}