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), außerdem die Kalender-Sitzungsanzeige (3.3.4) und die Mindeststichprobe der /// Fehlzeiten-Warnung (5.2.3). Deckt nicht die übrigen, unveränderten Dashboard-Bausteine /// (offene Aufgaben, ...). public sealed class DashboardViewModelTests { [Fact] public void LeereHinweisbereiche_WerdenAusgeblendetUndZusammenfassungBleibtKompakt() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }); Assert.False(vm.ExcusesCard.EffectiveIsVisible); Assert.False(vm.CorrectionsCard.EffectiveIsVisible); Assert.False(vm.AlertsCard.EffectiveIsVisible); Assert.Equal("0 offene Punkte", vm.AttentionSummary); Assert.True(vm.TodayCard.EffectiveIsVisible); Assert.True(vm.CalendarCard.EffectiveIsVisible); } 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, FakeEntries? entries = null, FakeAnnualPlanEvents? annualPlanEvents = null, List? allGroups = null) { lessons ??= new FakeLessons(); lessons.Add(lesson); return new DashboardViewModel( new FakeGroups(allGroups ?? [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([]), entries ?? 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(), annualPlanEvents); } [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 Kalenderauswahl_SortiertKurseDesTagesVorDenAlphabetischenRest() { var today = DateOnly.FromDateTime(DateTime.Today); var selectedDate = today.AddDays(1); var alphabeticallyFirst = new LearningGroup { Name = "10a" }; var selectedDayGroup = new LearningGroup { Name = "WAT 10c" }; var lessons = new FakeLessons(); lessons.Add(new Lesson { GroupId = selectedDayGroup.Id, Date = selectedDate }); var vm = BuildVm(alphabeticallyFirst, new Lesson { GroupId = alphabeticallyFirst.Id, Date = today }, lessons: lessons, allGroups: [alphabeticallyFirst, selectedDayGroup]); vm.SelectCalendarDayCommand.Execute(vm.CalendarDays.Single(d => d.Date == selectedDate)); Assert.Equal(selectedDayGroup.Id, vm.CurrentGroups[0].GroupId); Assert.True(vm.CurrentGroups[0].IsOnSelectedDay); Assert.Equal(alphabeticallyFirst.Id, vm.CurrentGroups[1].GroupId); } [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 Kalender_ZeigtMehrtaegigenJahresplanParallelZumUnterrichtAn() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var annualPlan = new FakeAnnualPlanEvents(); annualPlan.Add(new AnnualPlanEvent { ExternalId = "fahrt", Title = "Klassenfahrt 6a", Description = "Aushang beachten", Location = "Jugendherberge", CalendarGroup = "Lehrkräfte", StartDate = today, EndDate = today.AddDays(2), StartTime = new TimeOnly(13, 30), EndTime = new TimeOnly(15, 0), IsAllDay = false, }); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today, Topic = "Redox" }, annualPlanEvents: annualPlan); var day = vm.CalendarDays.Single(d => d.Date == today); Assert.True(day.HasLesson); Assert.True(day.HasAnnualPlanEvent); vm.SelectCalendarDayCommand.Execute(day); Assert.Contains(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.Lesson); var annual = Assert.Single(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.AnnualPlan); Assert.Equal("Klassenfahrt 6a", annual.Title); Assert.Equal("Aushang beachten", annual.Description); Assert.Contains("13:30", annual.Subtitle); Assert.Null(annual.GroupId); } [Fact] public void Kalender_AusStundeErzeugteSitzungErzeugtKeinenDoppelteintrag() { // Regressionstest (Nutzer-Feedback): "Sitzung erzeugen" (3.3.1) verknüpft die Sitzung über // LessonId mit der Stunde — dieselbe Unterrichtsstunde darf im Kalender nicht zusätzlich // als eigener Sitzungstermin auftauchen. var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var lesson = new Lesson { GroupId = group.Id, Date = today, Topic = "Brechung" }; var session = new ParticipationSession { GroupId = group.Id, Date = today, LessonId = lesson.Id, Comment = "Brechung" }; var vm = BuildVm(group, lesson, sessions: new FakeSessions([session])); var day = vm.CalendarDays.Single(d => d.Date == today); Assert.True(day.HasLesson); Assert.False(day.HasSession); vm.SelectCalendarDayCommand.Execute(day); Assert.Single(vm.SelectedDayEvents); Assert.DoesNotContain(vm.SelectedDayEvents, e => e.Kind == CalendarEventKind.ParticipationSession); } [Fact] public void Kalender_EigeneKlasseAmStundenplanTagOhneLessonZeigtDenRingTrotzdem() { // Regressionstest (Nutzer-Feedback): der "Meine Klasse"-Ring soll schon laut Stundenplan // gelten, nicht erst, sobald für den Tag eine Lesson angelegt wurde. var today = DateOnly.FromDateTime(DateTime.Today); var tomorrow = today.AddDays(1); var group = new LearningGroup { Name = "9c", IsOwnClass = true }; var slots = new FakeTimetableSlots(); slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = tomorrow.DayOfWeek, PeriodNumber = 3 }); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, slots: slots); var day = vm.CalendarDays.Single(d => d.Date == tomorrow); Assert.True(day.IsOwnClassDay); } [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); } [Fact] public void AttendanceWarnings_KleineStichprobeMitHoherQuoteWirdNichtGemeldet() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; var sessions = new FakeSessions([]); var entries = new FakeEntries(); // Nur 3 erfasste Termine, alle unentschuldigt (100 %) — Stichprobe zu klein für eine Meldung. for (var i = 0; i < 3; i++) { var session = new ParticipationSession { GroupId = group.Id, Date = today.AddDays(-i - 1) }; sessions.Save(session); entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.Unexcused }); } var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, students: new FakeStudents([student]), sessions: sessions, entries: entries); Assert.Empty(vm.AttendanceWarnings); } [Fact] public void AttendanceWarnings_MeldetHoheQuoteAbMindeststichprobe() { var today = DateOnly.FromDateTime(DateTime.Today); var group = new LearningGroup { Name = "9c" }; var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; var sessions = new FakeSessions([]); var entries = new FakeEntries(); // 10 erfasste Termine, 3 davon unentschuldigt (30 % > 20 % Schwelle) — genug Stichprobe. for (var i = 0; i < 10; i++) { var session = new ParticipationSession { GroupId = group.Id, Date = today.AddDays(-i - 1) }; sessions.Save(session); var status = i < 3 ? AttendanceStatus.Unexcused : AttendanceStatus.Present; entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = status }); } var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, students: new FakeStudents([student]), sessions: sessions, entries: entries); var item = Assert.Single(vm.AttendanceWarnings); Assert.Equal(student.FullName, item.StudentName); Assert.Equal(30.0, item.AbsenceRatePercent); } [Fact] public async Task AddTask_SpeichertErgebnisUndAktualisiertOffeneAufgaben() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var tasks = new FakeWorkTasks(); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, tasks: tasks); vm.OnAddTask = startAsReminder => Task.FromResult( new WorkTask { Title = "Ansage an die Klasse", Kind = startAsReminder ? TaskKind.Reminder : TaskKind.WorkItem }); await vm.AddReminderCommand.ExecuteAsync(null); Assert.Single(tasks.GetAll()); Assert.Contains(vm.OpenTasks, t => t.Title == "Ansage an die Klasse" && t.IsReminder); } [Fact] public async Task AddTask_OhneDelegatMachtNichts() { var group = new LearningGroup { Name = "9c" }; var today = DateOnly.FromDateTime(DateTime.Today); var tasks = new FakeWorkTasks(); var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, tasks: tasks); await vm.AddTaskCommand.ExecuteAsync(null); Assert.Empty(tasks.GetAll()); } [Fact] public void WeatherWarningItem_BereitetAmtlicheWarnungFuerDashboardAuf() { var warning = new WeatherWarning { Event = "GEWITTER", Headline = "Amtliche Warnung vor Gewitter", Description = "Es treten Gewitter auf.", Instruction = "Gebäude aufsuchen.", Severity = "Severe", Onset = new DateTime(2026, 8, 23, 16, 0, 0, DateTimeKind.Utc), Expires = new DateTime(2026, 8, 23, 18, 0, 0, DateTimeKind.Utc), }; var item = new DashboardWeatherWarningItem(warning); Assert.Equal("Amtliche Warnung vor Gewitter", item.Headline); Assert.Equal("#D32F2F", item.SeverityColor); Assert.Contains("Uhr", item.PeriodDisplay); } }