using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Desktop.ViewModels; using Xunit; namespace LehrerApp.Desktop.Tests; /// Tests für 9.1 (heutige Stunden mit Uhrzeit und Raum im Dashboard) und 9.2 (Absprung von einer /// Stunde). Deckt nur die neue Zeit-/Raum-Auflösung ab, nicht die übrigen, unveränderten /// Dashboard-Bausteine (Kalender, offene Aufgaben, Fehlzeiten-Warnungen, ...). public sealed class DashboardViewModelTests { private static PeriodScheduleService NewPeriodSchedule() { var tempPath = System.IO.Path.Combine( System.IO.Path.GetTempPath(), $"lehrerapp-dashboardvm-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(tempPath); return new PeriodScheduleService(tempPath); } private static DashboardSettingsService NewDashboardSettings() { var tempPath = System.IO.Path.Combine( System.IO.Path.GetTempPath(), $"lehrerapp-dashboardsettings-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(tempPath); return new DashboardSettingsService(tempPath); } private static SchoolCalendarSettingsService NewCalendarSettings() { var tempPath = System.IO.Path.Combine( System.IO.Path.GetTempPath(), $"lehrerapp-schoolcalendar-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(tempPath); return new SchoolCalendarSettingsService(tempPath); } private static DashboardViewModel BuildVm(LearningGroup group, Lesson lesson, FakeTimetableSlots? slots = null, PeriodScheduleService? periodSchedule = null, FakeExams? exams = null, FakeResults? results = null, FakeGrades? grades = null, FakeReportGrades? reportGrades = null, FakeMemberships? memberships = null, FakeWorkTasks? tasks = null, FakeStudents? students = null, FakeDocumentation? documentation = null, DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null, FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null, FakeSessions? sessions = null) { lessons ??= new FakeLessons(); lessons.Add(lesson); return new DashboardViewModel( new FakeGroups([group]), new FakeSubjects([]), lessons, exams ?? new FakeExams([]), results ?? new FakeResults(), grades ?? new FakeGrades(), reportGrades ?? new FakeReportGrades(), memberships ?? new FakeMemberships([]), tasks ?? new FakeWorkTasks(), sessions ?? new FakeSessions([]), new FakeEntries(), students ?? new FakeStudents([]), documentation ?? new FakeDocumentation(), slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(), new AttendanceBalanceService(), new SchoolYearService(), dashboardSettings ?? NewDashboardSettings(), schoolHolidays ?? new FakeSchoolHolidays(), new PublicHolidayService(), NewCalendarSettings(), substitutions ?? new FakeSubstitutionEntries()); } [Fact] public void TodaysLessons_LoestRaumUeberPassendenStundenplanSlotAuf() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3, Room = "R204" }); var vm = BuildVm(group, lesson, slots: slots); var item = Assert.Single(vm.TodaysLessons); Assert.Equal("R204", item.Room); Assert.True(item.HasRoom); } [Fact] public void TodaysLessons_OhnePassendenSlot_HatKeinenRaum() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" }; var vm = BuildVm(group, lesson); var item = Assert.Single(vm.TodaysLessons); Assert.False(item.HasRoom); Assert.Equal("", item.Room); } [Fact] public void TodaysLessons_ZeitKommtAusLessonStartTimeWennGesetzt() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox", StartTime = new TimeOnly(9, 15), }; var periodSchedule = NewPeriodSchedule(); periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(10, 0), End = new TimeOnly(10, 45) }]); var vm = BuildVm(group, lesson, periodSchedule: periodSchedule); Assert.Equal("09:15", Assert.Single(vm.TodaysLessons).TimeDisplay); } [Fact] public void TodaysLessons_OhneStartTime_FaelltAufStundenrasterZurueck() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" }; var periodSchedule = NewPeriodSchedule(); periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(10, 0), End = new TimeOnly(10, 45) }]); var vm = BuildVm(group, lesson, periodSchedule: periodSchedule); Assert.Equal("10:00", Assert.Single(vm.TodaysLessons).TimeDisplay); } [Fact] public void OpenLesson_RuftNavigationMitGruppenIdAuf() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" }; var vm = BuildVm(group, lesson); Guid? navigatedTo = null; vm.OnNavigateToLesson = id => navigatedTo = id; vm.OpenLessonCommand.Execute(vm.TodaysLessons[0]); Assert.Equal(group.Id, navigatedTo); } [Fact] public void UpcomingDates_BuendeltKlausurenFristenUndFoerderplanPruefungen() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var exams = new FakeExams([ new Exam { GroupId = group.Id, Title = "Chemie-Test", Date = today.AddDays(5) }, ]); var tasks = new FakeWorkTasks(); tasks.Add(new WorkTask { Title = "Notenschluss", DueDate = today.AddDays(10) }); var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; var documentation = new FakeDocumentation(); documentation.Add(new Documentation { StudentId = student.Id, Type = DocumentationType.SupportPlan, Title = "Leseförderung", SupportData = new SupportData { Status = SupportStatus.Active, ReviewDate = today.AddDays(3) }, }); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: exams, tasks: tasks, students: new FakeStudents([student]), documentation: documentation); Assert.Contains(vm.UpcomingDates, i => i.Kind == UpcomingDateKind.Exam && i.Title == "Chemie-Test"); Assert.Contains(vm.UpcomingDates, i => i.Kind == UpcomingDateKind.Deadline && i.Title == "Notenschluss"); Assert.Contains(vm.UpcomingDates, i => i.Kind == UpcomingDateKind.SupportPlan && i.StudentId == student.Id); } [Fact] public void OpenCorrections_ZeigtBewertungsfortschrittJeKlausur() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var exam = new Exam { GroupId = group.Id, Title = "Klausur 1", Date = today.AddDays(-2), Status = ExamStatus.Conducted }; var anna = new Student { FirstName = "Anna", LastName = "A" }; var ben = new Student { FirstName = "Ben", LastName = "B" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = group.Id, StudentId = anna.Id }, new GroupMembership { GroupId = group.Id, StudentId = ben.Id }, ]); var results = new FakeResults(); results.Add(new ExamResult { ExamId = exam.Id, StudentId = anna.Id, Grade = "2" }); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: new FakeExams([exam]), results: results, memberships: memberships, students: new FakeStudents([anna, ben])); var correction = Assert.Single(vm.OpenCorrections); Assert.Equal(1, correction.Completed); Assert.Equal(2, correction.Total); Assert.Equal(50, correction.Percent); } [Fact] public void Alerts_ErkenntDeutlichenNotenabfall() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c", GradingSystem = GradingSystem.Grades1To6 }; var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = group.Id, StudentId = student.Id }, ]); var grades = new FakeGrades(); grades.Add(new Grade { GroupId = group.Id, StudentId = student.Id, Date = today.AddDays(-20), Value = "2" }); grades.Add(new Grade { GroupId = group.Id, StudentId = student.Id, Date = today.AddDays(-15), Value = "2" }); grades.Add(new Grade { GroupId = group.Id, StudentId = student.Id, Date = today.AddDays(-10), Value = "4" }); grades.Add(new Grade { GroupId = group.Id, StudentId = student.Id, Date = today.AddDays(-5), Value = "4" }); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, grades: grades, memberships: memberships, students: new FakeStudents([student])); Assert.Contains(vm.Alerts, a => a.StudentId == student.Id && a.KindLabel == "Notenabfall"); } [Fact] public void Kalenderauswahl_ZeigtTermineDesGeklicktenTages() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var examDate = today.AddDays(2); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: new FakeExams([new Exam { GroupId = group.Id, Title = "Test", Date = examDate }])); var day = vm.CalendarDays.Single(d => d.Date == examDate); vm.SelectCalendarDayCommand.Execute(day); Assert.True(day.IsSelected); Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.Exam && e.Title == "Test"); } [Fact] public void Kalender_ZeigtMitarbeitssitzungenAlsEigenenTermintyp() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var sessionDate = today.AddDays(3); var session = new ParticipationSession { GroupId = group.Id, Date = sessionDate, Comment = "Aufsatz" }; var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, sessions: new FakeSessions([session])); var day = vm.CalendarDays.Single(d => d.Date == sessionDate); Assert.True(day.HasSession); vm.SelectCalendarDayCommand.Execute(day); Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.ParticipationSession && e.Subtitle == "Aufsatz"); } [Fact] public void DashboardKacheln_SichtbarkeitUndReihenfolgeWerdenGespeichert() { var settings = NewDashboardSettings(); var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, dashboardSettings: settings); vm.TodayCard.IsVisible = false; vm.MoveCardDownCommand.Execute(vm.TasksCard); var saved = settings.Load(); Assert.False(saved.Single(c => c.Key == "today").IsVisible); Assert.True(saved.FindIndex(c => c.Key == "tasks") > 1); } // ── Ungeplante Stunden (Nutzer-Feedback) ────────────────────────────────── [Fact] public void UnplannedLessons_StundeplanSlotOhneLessonMitThema_ErscheintInDerListe() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); // "lesson" liegt bewusst weit in der Vergangenheit, damit sie nicht in den heute/morgen- // Vorgriff fällt und den Test verfälscht. var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" }; var vm = BuildVm(group, pastLesson, slots: slots); var item = Assert.Single(vm.UnplannedLessons); Assert.Equal(group.Id, item.GroupId); Assert.Equal(1, item.PeriodNumber); Assert.Equal("Heute", item.DateDisplay); } [Fact] public void UnplannedLessons_LessonMitThemaVorhanden_ErscheintNicht() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 1, Topic = "Redoxreaktionen" }; var vm = BuildVm(group, lesson, slots: slots); Assert.Empty(vm.UnplannedLessons); } [Fact] public void UnplannedLessons_GruppeOhnePlanungsbedarf_WirdIgnoriert() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "Klassenrat", RequiresLessonPlanning = false }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" }; var vm = BuildVm(group, pastLesson, slots: slots); Assert.Empty(vm.UnplannedLessons); } [Fact] public void UnplannedLessons_Feiertag_WirdUebersprungen() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 }); var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" }; var schoolHolidays = new FakeSchoolHolidays(); schoolHolidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = today, EndDate = today.AddDays(1) }); var vm = BuildVm(group, pastLesson, slots: slots, schoolHolidays: schoolHolidays); Assert.Empty(vm.UnplannedLessons); } [Fact] public void UnplannedLessons_DoppelstundeMitThemaAmErstenSlot_ZweiterSlotWirdNichtGemeldet() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3 }); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 4 }); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" }; var vm = BuildVm(group, lesson, slots: slots); Assert.Empty(vm.UnplannedLessons); } [Fact] public void UnplannedLessons_DoppelstundeOhneThema_BeideSlotsWerdenGemeldet() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3 }); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 4 }); var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "" }; var vm = BuildVm(group, lesson, slots: slots); Assert.Equal(2, vm.UnplannedLessons.Count); Assert.Contains(vm.UnplannedLessons, i => i.PeriodNumber == 3); Assert.Contains(vm.UnplannedLessons, i => i.PeriodNumber == 4); } [Fact] public void UnplannedLessons_StundenausfallEingetragen_WirdNichtGemeldet() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 5 }); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 6 }); var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 5, Topic = "Alt" }; var substitutions = new FakeSubstitutionEntries(); substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 5 }); substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 6 }); var vm = BuildVm(group, pastLesson, slots: slots, substitutions: substitutions); Assert.Empty(vm.UnplannedLessons); } }