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>
101 lines
4.2 KiB
Swift
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)))
|
|
}
|
|
}
|