// // Bible_WeekTests.swift // Bible-WeekTests // // Testes do Plano de 180 Dias e do lembrete diário (lógica pura, // sem depender do UNUserNotificationCenter real). // import Foundation import Testing import UserNotifications @testable import Bible_Week // MARK: - Cronograma do plano struct PlanScheduleTests { @Test func planHas180Days() { #expect(PlanSchedule.days.count == 180) #expect(PlanSchedule.days.first?.day == 1) #expect(PlanSchedule.days.last?.day == 180) } @Test func everyDayHasAllThreeTracksWithChapters() { for day in PlanSchedule.days { #expect(day.readings.count == PlanTrack.allCases.count) for reading in day.readings { #expect(!reading.chapters.isEmpty, "Dia \(day.day), trilha \(reading.track) sem capítulos") #expect(!reading.label.isEmpty) } } } /// Cada trilha deve cobrir todos os seus capítulos, em ordem canônica, /// sem lacunas e sem repetições — a Bíblia inteira em 180 dias. @Test func scheduleCoversEntireCanonExactlyOnce() { for track in PlanTrack.allCases { let scheduled = PlanSchedule.days.flatMap { day in day.reading(for: track)?.chapters ?? [] } let expected = track.books.flatMap { book in (1...book.chapterCount).map { ChapterRef(bookCode: book.code, bookName: book.name, chapter: $0) } } #expect(scheduled == expected, "Trilha \(track) não cobre o cânon exatamente") } let total = PlanTrack.allCases.reduce(0) { sum, track in sum + track.books.reduce(0) { $0 + $1.chapterCount } } #expect(total == 1189) } @Test func labelsCollapseConsecutiveChaptersIntoRanges() { let reading = TrackReading(track: .history, chapters: [ ChapterRef(bookCode: "GEN", bookName: "Gênesis", chapter: 34), ChapterRef(bookCode: "GEN", bookName: "Gênesis", chapter: 35), ChapterRef(bookCode: "GEN", bookName: "Gênesis", chapter: 36), ]) #expect(reading.label == "Gênesis 34–36") let crossBook = TrackReading(track: .history, chapters: [ ChapterRef(bookCode: "GEN", bookName: "Gênesis", chapter: 50), ChapterRef(bookCode: "EXO", bookName: "Êxodo", chapter: 1), ]) #expect(crossBook.label == "Gênesis 50 e Êxodo 1") } } // MARK: - Estado do plano struct ReadingPlanStateTests { @Test func currentDayAdvancesWhenAllTracksCompleted() { let plan = ReadingPlanState() #expect(plan.currentDay == 1) // Concluir só uma trilha não avança o dia. plan.setCompleted(true, day: 1, track: .history) #expect(plan.currentDay == 1) plan.setCompleted(true, day: 1, track: .wisdom) plan.setCompleted(true, day: 1, track: .newTestament) #expect(plan.currentDay == 2) #expect(plan.completedDaysCount == 1) } @Test func settingCompletedTwiceDoesNotDuplicate() { let plan = ReadingPlanState() plan.setCompleted(true, day: 1, track: .history) plan.setCompleted(true, day: 1, track: .history) #expect(plan.completedKeys.count == 1) plan.setCompleted(false, day: 1, track: .history) #expect(plan.completedKeys.isEmpty) } @Test func changingReminderTimeReplacesPreviousValue() { let plan = ReadingPlanState() #expect(plan.reminderHour == 20) #expect(plan.reminderMinute == 0) plan.reminderHour = 7 plan.reminderMinute = 30 #expect(plan.reminderTimeText == "07:30") } @Test func pausePreservesReminderPreference() { let plan = ReadingPlanState() plan.reminderEnabled = true plan.reminderHour = 7 plan.reminderMinute = 30 plan.isPaused = true #expect(plan.reminderEnabled) #expect(plan.reminderHour == 7) #expect(plan.reminderMinute == 30) plan.isPaused = false #expect(plan.reminderEnabled) #expect(plan.reminderTimeText == "07:30") } @Test func planCompletionDetection() { let plan = ReadingPlanState() for day in 1...PlanSchedule.totalDays { for track in PlanTrack.allCases { plan.setCompleted(true, day: day, track: track) } } #expect(plan.isCompleted) #expect(plan.completedDaysCount == 180) #expect(plan.progress == 1.0) } @Test func restartClearsProgressButKeepsReminderPreference() { let plan = ReadingPlanState() plan.reminderEnabled = true plan.reminderHour = 20 plan.reminderMinute = 0 plan.setCompleted(true, day: 1, track: .history) plan.isPaused = true plan.restart() #expect(plan.completedKeys.isEmpty) #expect(plan.currentDay == 1) #expect(!plan.isPaused) // Preferências de lembrete sobrevivem ao reinício. #expect(plan.reminderEnabled) #expect(plan.reminderTimeText == "20:00") } } // MARK: - Conteúdo do lembrete struct ReminderContentTests { @Test func contentIncludesJourneyDayWhenAvailable() { let content = ReadingReminderService.makeContent(currentDay: 34, soundEnabled: true) #expect(content.title.contains("34")) #expect(!content.body.isEmpty) #expect(content.sound != nil) } @Test func contentFallsBackToDefaultTitleWithoutDay() { let content = ReadingReminderService.makeContent(currentDay: nil, soundEnabled: true) #expect(content.title == "📖 Sua leitura de hoje") } @Test func soundOffProducesSilentNotification() { let content = ReadingReminderService.makeContent(currentDay: 1, soundEnabled: false) #expect(content.sound == nil) } @Test func userInfoRoutesToReadingPlanToday() { let content = ReadingReminderService.makeContent(currentDay: 1, soundEnabled: true) #expect(content.userInfo[NotificationUserInfo.destinationKey] as? String == NotificationUserInfo.destinationReadingPlan) #expect(content.userInfo[NotificationUserInfo.planIdKey] as? String == "bible-180") #expect(content.userInfo[NotificationUserInfo.screenKey] as? String == "today") } @Test func messagesAvoidPressuringLanguage() { for message in ReadingReminderService.motivationalMessages { #expect(!message.lowercased().contains("streak")) #expect(!message.lowercased().contains("sequência")) #expect(!message.lowercased().contains("esqueceu")) #expect(!message.lowercased().contains("perdendo")) } } }