// // RuleStatusTests.swift // SeveredTests // import Foundation import Testing @testable import Severed @MainActor @Suite("Rule status derivation and labels") struct RuleStatusTests { /// 09:00–17:00 weekdays schedule rule, like the video's "Work Time". func workTime(hardMode: Bool = false) -> BlockingRule { BlockingRule(name: "Work Time", hardMode: hardMode) } @Test("Disabled rules report disabled regardless of the clock") func disabled() { let rule = workTime() rule.isEnabled = false #expect(rule.status(at: date(2025, 1, 6, 10, 0), calendar: utc) == .disabled) } @Test("Enabled rule inside its window is active until the window ends") func active() { let status = workTime().status(at: date(2025, 1, 6, 10, 0), calendar: utc) #expect(status == .active(until: date(2025, 1, 6, 17, 0))) #expect(status.isActive) } @Test("Enabled rule outside its window is upcoming") func upcoming() { let status = workTime().status(at: date(2025, 1, 6, 18, 0), calendar: utc) #expect(status == .upcoming(startsAt: date(2025, 1, 7, 9, 0))) #expect(!status.isActive) } @Test("A rule with no days is dormant") func dormant() { let rule = workTime() rule.days = [] #expect(rule.status(at: date(2025, 1, 6, 10, 0), calendar: utc) == .dormant) } @Test("A paused rule reports paused until the window ends") func paused() { let rule = workTime() rule.pausedUntil = date(2025, 1, 6, 17, 0) let status = rule.status(at: date(2025, 1, 6, 10, 0), calendar: utc) #expect(status == .paused(until: date(2025, 1, 6, 17, 0))) #expect(!status.isActive) } @Test("An expired pause no longer affects status") func expiredPause() { let rule = workTime() rule.pausedUntil = date(2025, 1, 6, 9, 30) let status = rule.status(at: date(2025, 1, 6, 10, 0), calendar: utc) #expect(status == .active(until: date(2025, 1, 6, 17, 0))) } @Test("Time-limit rules are never schedule-active") func timeLimitNeverActive() { let rule = BlockingRule(name: "Time Keeper", kind: .timeLimit) let status = rule.status(at: date(2025, 1, 6, 10, 0), calendar: utc) #expect(!status.isActive) if case .upcoming = status {} else { Issue.record("Expected upcoming, got \(status)") } } @Test("Active label matches the reference style: hours round up") func activeLabel() { // 11:28 → 17:00 is 5h32m; the reference app shows "6h left". let status = workTime().status(at: date(2025, 1, 6, 11, 28), calendar: utc) #expect(status.label(relativeTo: date(2025, 1, 6, 11, 28)) == "6h left") } @Test("Upcoming label matches the reference style") func upcomingLabel() { // Friday 11:28 → Saturday 09:00 is 21h32m; the reference shows "Starts in 22h". let weekend = BlockingRule(name: "Weekend Zen", days: Weekday.weekends) let friday = date(2025, 1, 10, 11, 28) #expect(weekend.status(at: friday, calendar: utc).label(relativeTo: friday) == "Starts in 22h") } @Test("Countdown formatting tiers", arguments: [ (30, "30m"), (59, "59m"), (60, "1h"), (90, "2h"), (6 * 60, "6h"), (47 * 60, "47h"), (49 * 60, "2d"), (75 * 60, "3d"), ]) func countdownTiers(minutes: Int, expected: String) { let now = date(2025, 1, 6, 0, 0) let target = utc.date(byAdding: .minute, value: minutes, to: now)! #expect(RuleStatus.countdown(from: now, to: target) == expected) } @Test("Countdown never reads below one minute") func countdownFloor() { let now = date(2025, 1, 6, 0, 0) #expect(RuleStatus.countdown(from: now, to: now.addingTimeInterval(5)) == "1m") } @Test("Static labels") func staticLabels() { let now = date(2025, 1, 6, 0, 0) #expect(RuleStatus.disabled.label(relativeTo: now) == "Disabled") #expect(RuleStatus.dormant.label(relativeTo: now) == "No days selected") #expect(RuleStatus.paused(until: now).label(relativeTo: now) == "Paused") } }