Files
LehrerApp/LehrerApp.Desktop.Tests/TimetableViewModelTests.cs
T
adminandClaude Sonnet 5 d9d007cd4b feat: Unterrichtsmodus - konsolidierte Ansicht für die laufende Stunde
Verlaufsplan und Sitzplan bisher über mehrere Tabs verteilt, obwohl im Unterricht selbst beides
gleichzeitig gebraucht wird. Neuer Button "▶ Unterricht" in der "Heute"-Tagesliste des
Stundenplans (nur bei vorhandener Lesson) öffnet ein maximiertes Fenster: links der
schreibgeschützte Verlaufsplan als kompakte Kartenliste, rechts der volle, unverändert
wiederverwendete SeatingPlanTabView (Drag&Drop, Schnellbewertung, Situations-Tags, PDF-Export).
TeachingModeViewModel baut auf dem bestehenden LessonViewerViewModel auf, dadurch kommen "Zur
Mitarbeit"/"Zu den Noten" ohne Duplizierung mit.

Neue SeatingPlanTabViewModel.SelectOrCreateSessionForLesson verknüpft die Mitarbeitssitzung
automatisch mit der konkreten Stunde - anders als die bestehende EnsureTodaySession darf das hier
sofort beim Start passieren, da durch den expliziten Klick für genau diese Stunde eindeutig
feststeht, worum es geht (keine Geistersitzungs-Gefahr wie beim bloßen Tab-Öffnen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 14:53:16 +02:00

1045 lines
48 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Planning;
using LehrerApp.Desktop.ViewModels.Settings;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class TimetableViewModelTests
{
private static TimetableViewModel BuildViewModel(
FakeTimetableSlots slots, FakeGroups groups, FakeSchoolHolidays? holidays = null,
FakeSubjects? subjects = null, FakeLessons? lessons = null, FakeExams? exams = null,
SchoolCalendarSettingsService? calendarSettings = null,
FakeSupervisionDuties? supervisionDuties = null, FakeSubstitutionEntries? substitutions = null)
{
// Bewusst kein "using": SchoolCalendarSettingsService liest den Pfad nur bei Bedarf
// (SetState), das Verzeichnis muss über die Lebensdauer des ViewModels bestehen bleiben.
var tempPath = System.IO.Path.Combine(
System.IO.Path.GetTempPath(), $"lehrerapp-timetablevm-tests-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempPath);
return new TimetableViewModel(
slots, groups, subjects ?? new FakeSubjects([]),
lessons ?? new FakeLessons(), exams ?? new FakeExams([]),
holidays ?? new FakeSchoolHolidays(),
calendarSettings ?? new SchoolCalendarSettingsService(tempPath),
new PublicHolidayService(), new SchoolYearService(),
supervisionDuties ?? new FakeSupervisionDuties(), substitutions ?? new FakeSubstitutionEntries());
}
/// Nächstes Datum ab (inkl.) <paramref name="from"/>, das auf einen Wochentag Mo-Fr fällt —
/// nur diese Tage haben eine Spalte im Raster. Für Badge-Tests reicht ein beliebiger
/// Mo-Fr-Wochentag: die Badges hängen nur am Wochentag (nicht am konkreten Kalenderdatum),
/// da `TimetableSlot` ein wiederkehrendes Muster ohne Datum ist.
private static DateOnly NextGridWeekday(DateOnly from)
{
var d = from;
while (d.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday) d = d.AddDays(1);
return d;
}
/// Datum des angegebenen Wochentags in der laufenden Kalenderwoche — exakt wie
/// `TimetableViewModel.BuildWeekOverview` es berechnet, für Tests der Wochenkachel-Inhalte
/// (Thema, Klausur am Tag), die anders als die Badges an ein konkretes Datum gebunden sind.
private static DateOnly DateInCurrentWeek(DayOfWeek weekday)
{
var today = DateOnly.FromDateTime(DateTime.Today);
var monday = today.AddDays(-((int)today.DayOfWeek + 6) % 7);
return monday.AddDays((int)weekday - (int)DayOfWeek.Monday);
}
[Fact]
public void Load_BautEineZelleProWochentagUndStundePlusKopfzeilen()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
// 1 Ecke + 5 Wochentage + 10 Stunden × (1 Label + 5 Zellen)
Assert.Equal(6 + 10 * 6, vm.Cells.Count);
Assert.Equal(6 + 10 * 6, vm.WeekItems.Count);
}
[Fact]
public void Load_ZeigtZugewieseneGruppeInDerPassendenZelle()
{
var weekday = NextGridWeekday(DateOnly.FromDateTime(DateTime.Today));
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = weekday.DayOfWeek, PeriodNumber = 1, Room = "R204" });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var cell = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 1);
Assert.True(cell.IsAssigned);
Assert.Equal("Q1 Chemie · R204", cell.Text);
}
[Fact]
public void HoursWarnings_WeichtDieZugewieseneStundenzahlAb_ZeigtWarnung()
{
var group = new LearningGroup { Name = "Q1 Chemie", SchoolYear = new SchoolYearService().CurrentSchoolYear(), HoursPerWeek = 3 };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
Assert.Single(vm.HoursWarnings);
Assert.Contains("1 von 3", vm.HoursWarnings[0].Text);
}
[Fact]
public void HoursWarnings_PassendeStundenzahl_KeineWarnung()
{
var group = new LearningGroup { Name = "Q1 Chemie", SchoolYear = new SchoolYearService().CurrentSchoolYear(), HoursPerWeek = 1 };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
Assert.Empty(vm.HoursWarnings);
}
// ── "Heute"-Ansicht: Tagesliste (Nutzer-Feedback: nicht-editierende Standardansicht) ────
[Fact]
public void Load_ZeigtHeutigeStundeMitGruppeUndRaum()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3, Room = "R204" });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var item = Assert.Single(vm.TodayItems);
Assert.Equal("Q1 Chemie", item.GroupName);
Assert.Equal("R204", item.Room);
Assert.Equal(3, item.PeriodNumber);
}
[Fact]
public void Load_HeutigeStundeMitLektionUndKlausur_ZeigtBeides()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = today, Topic = "Redoxreaktionen" });
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today, Title = "Klausur Nr. 2" }]);
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons, exams: exams);
var item = Assert.Single(vm.TodayItems);
Assert.Equal("Redoxreaktionen", item.LessonTopic);
Assert.Equal("Klausur Nr. 2", item.ExamTitle);
Assert.True(item.HasExam);
}
[Fact]
public void Load_HeutigeStunde_VorherigeUnkontrollierteHausaufgabe_ZeigtBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3" });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.True(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_HausaufgabeKontrolliert_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson
{
GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3", HomeworkChecked = true,
});
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_HausaufgabenKontrolleBewusstAbgeschaltet_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson
{
GroupId = group.Id, Date = today.AddDays(-3), Homework = "S. 42, Aufgabe 3",
HomeworkCheckDismissed = true,
});
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_HeutigeStunde_VorherigeStundeOhneHausaufgabe_KeinBadge()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = today.AddDays(-3) });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasUnhandledHomework);
}
[Fact]
public void Load_Wochenkachel_VorherigeUnkontrollierteHausaufgabe_ZeigtBadge()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Wednesday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Wednesday, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = date.AddDays(-2), Homework = "Vokabeln lernen" });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Wednesday && c.PeriodNumber == 1);
Assert.True(cell.HasUnhandledHomework);
}
[Fact]
public void OpenGroup_RuftOnNavigateToGroupMitDerGroupIdAuf()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
Guid? navigatedTo = null;
vm.OnNavigateToGroup = id => navigatedTo = id;
vm.OpenGroupCommand.Execute(vm.TodayItems[0].GroupId);
Assert.Equal(group.Id, navigatedTo);
}
// ── Direktsprung in den Verlaufsplan-Viewer (4.5.2) ──────────────────────────
[Fact]
public async Task OpenTodayLesson_MitVorhandenerLesson_OeffnetDenViewerStattZurGruppeZuNavigieren()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
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 = "Redox" };
var lessons = new FakeLessons();
lessons.Add(lesson);
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
Lesson? openedLesson = null;
Guid? navigatedTo = null;
vm.OnOpenLessonViewer = l => { openedLesson = l; return Task.CompletedTask; };
vm.OnNavigateToGroup = id => navigatedTo = id;
await vm.OpenTodayLessonCommand.ExecuteAsync(vm.TodayItems[0]);
Assert.Equal(lesson.Id, openedLesson?.Id);
Assert.Null(navigatedTo);
Assert.Equal("Verlaufsplan ansehen", vm.TodayItems[0].OpenButtonLabel);
}
[Fact]
public async Task OpenTodayLesson_OhneVorhandeneLesson_NavigiertWieBisherZurGruppe()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
Guid? navigatedTo = null;
var viewerOpened = false;
vm.OnOpenLessonViewer = _ => { viewerOpened = true; return Task.CompletedTask; };
vm.OnNavigateToGroup = id => navigatedTo = id;
await vm.OpenTodayLessonCommand.ExecuteAsync(vm.TodayItems[0]);
Assert.Equal(group.Id, navigatedTo);
Assert.False(viewerOpened);
Assert.Equal("Zur Lerngruppe", vm.TodayItems[0].OpenButtonLabel);
}
// ── Unterrichtsmodus (14.x) ────────────────────────────────────────────────
[Fact]
public async Task StartTeachingMode_MitVorhandenerLesson_OeffnetDenUnterrichtsmodus()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
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 = "Redox" };
var lessons = new FakeLessons();
lessons.Add(lesson);
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
Lesson? openedLesson = null;
vm.OnOpenTeachingMode = l => { openedLesson = l; return Task.CompletedTask; };
await vm.StartTeachingModeCommand.ExecuteAsync(vm.TodayItems[0]);
Assert.Equal(lesson.Id, openedLesson?.Id);
}
[Fact]
public async Task StartTeachingMode_OhneVorhandeneLesson_TutNichts()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var opened = false;
vm.OnOpenTeachingMode = _ => { opened = true; return Task.CompletedTask; };
await vm.StartTeachingModeCommand.ExecuteAsync(vm.TodayItems[0]);
Assert.False(opened);
Assert.False(vm.TodayItems[0].HasLesson);
}
[Fact]
public async Task OpenWeekCell_MitVorhandenerLesson_OeffnetDenViewer()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var lesson = new Lesson { GroupId = group.Id, Date = date, LessonNumber = 1, Topic = "Redox" };
var lessons = new FakeLessons();
lessons.Add(lesson);
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
Lesson? openedLesson = null;
vm.OnOpenLessonViewer = l => { openedLesson = l; return Task.CompletedTask; };
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
await vm.OpenWeekCellCommand.ExecuteAsync(cell);
Assert.Equal(lesson.Id, openedLesson?.Id);
}
[Fact]
public void OpenSettings_RuftOnNavigateToSettingsAuf()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
SettingsTab? navigatedTab = null;
vm.OnNavigateToSettings = tab => navigatedTab = tab;
vm.OpenSettingsCommand.Execute(null);
Assert.Equal(SettingsTab.Holidays, navigatedTab);
}
// ── "Heute"-Ansicht: Wochenraster (Nutzer-Feedback, zweite Iteration) ────────────────────
[Fact]
public void Load_Wochenkachel_ZeigtFachKlasseRaumUndThema()
{
var subject = new Subject { Name = "Chemie", ShortName = "Ch" };
var group = new LearningGroup { Name = "Q1 Chemie", SubjectId = subject.Id };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1, Room = "R204" });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = date, Topic = "Redoxreaktionen" });
var vm = BuildViewModel(slots, new FakeGroups([group]), subjects: new FakeSubjects([subject]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.Equal("Ch", cell.SubjectLabel);
Assert.Equal("Q1 Chemie", cell.GroupName);
Assert.Equal("R204", cell.Room);
Assert.Equal("Redoxreaktionen", cell.Topic);
}
[Fact]
public void Load_Wochenkachel_UnbelegteZelle_IstNichtZugewiesen()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.False(cell.IsAssigned);
}
[Fact]
public void Load_Wochenkachel_KlausurAmTag_ZeigtKlausurIcon()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = date, Title = "Klausur" }]);
var vm = BuildViewModel(slots, new FakeGroups([group]), exams: exams);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.HasExam);
}
[Fact]
public void Load_Wochenkachel_TagInSchulferien_WirdAusgegraut()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var holidays = new FakeSchoolHolidays();
holidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = date, EndDate = date.AddDays(5) });
var vm = BuildViewModel(slots, new FakeGroups([group]), holidays);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.IsHoliday);
Assert.Equal("#BDBDBD", cell.ColorHex);
}
[Fact]
public void Load_Wochenkachel_TagAusserhalbVonFerien_WirdNichtAusgegraut()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.False(cell.IsHoliday);
}
[Fact]
public void Load_Wochenkachel_ExperimentInPhaseGeplant_ZeigtExperimentIcon()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = date, Phases = [new LessonPhaseStep { Activity = "Experiment: Redoxreihe" }] });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.HasExperiment);
}
[Fact]
public void Load_Wochenkachel_OhneExperimentErwaehnung_KeinExperimentIcon()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson { GroupId = group.Id, Date = date, Phases = [new LessonPhaseStep { Activity = "Stillarbeit" }] });
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.False(cell.HasExperiment);
}
[Fact]
public void Load_Wochenkachel_UnvollstaendigePhasendaten_VerursachenKeinenFehler()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var lessons = new FakeLessons();
lessons.Add(new Lesson
{
GroupId = group.Id,
Date = date,
Phases = [new LessonPhaseStep { Name = null!, Activity = null!, Material = "Experimentiermaterial" }],
});
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.HasExperiment);
}
[Fact]
public void Load_Wochenkachel_LetzteStundeVorKlausur_ZeigtIcon()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = date.AddDays(1), Title = "Klausur" }]);
var vm = BuildViewModel(slots, new FakeGroups([group]), exams: exams);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.IsLastBeforeExam);
}
[Fact]
public void Load_Wochenkachel_KeineAnstehendeKlausur_KeinIcon()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.False(cell.IsLastBeforeExam);
}
[Fact]
public void PreviousWeek_ZeigtVorherigeKalenderwoche()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
var initialLabel = vm.WeekRangeLabel;
vm.PreviousWeekCommand.Execute(null);
Assert.NotEqual(initialLabel, vm.WeekRangeLabel);
Assert.False(vm.IsCurrentWeek);
}
[Fact]
public void NextWeek_GefolgtVonCurrentWeek_KehrtZurLaufendenWocheZurueck()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
var initialLabel = vm.WeekRangeLabel;
vm.NextWeekCommand.Execute(null);
Assert.NotEqual(initialLabel, vm.WeekRangeLabel);
vm.CurrentWeekCommand.Execute(null);
Assert.Equal(initialLabel, vm.WeekRangeLabel);
Assert.True(vm.IsCurrentWeek);
}
[Fact]
public void NextWeek_BadgeGehoertZurAngezeigtenWoche_NichtZuHeute()
{
// Klausur liegt in der übernächsten Woche relativ zu "heute" — im Wochenraster einer
// Woche dahinter (also der übernächsten Woche selbst) muss "letzte Stunde vor Klausur"
// erscheinen, nicht bereits in der aktuellen Woche.
var group = new LearningGroup { Name = "Q1 Chemie" };
var currentWeekMonday = DateInCurrentWeek(DayOfWeek.Monday);
var examWeekMonday = currentWeekMonday.AddDays(14);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = examWeekMonday.AddDays(1), Title = "Klausur" }]);
var vm = BuildViewModel(slots, new FakeGroups([group]), exams: exams);
var currentWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.False(currentWeekCell.IsLastBeforeExam);
vm.NextWeekCommand.Execute(null);
vm.NextWeekCommand.Execute(null);
var examWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(examWeekCell.IsLastBeforeExam);
}
// ── Badge: letzte/vorletzte Stunde vor Ferien (Bearbeiten-Raster) ────────────────────────
[Fact]
public void Load_LetzteStundeVorFerien_ZeigtBadge1()
{
var weekday = NextGridWeekday(DateOnly.FromDateTime(DateTime.Today));
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = weekday.DayOfWeek, PeriodNumber = 1 });
var holidays = new FakeSchoolHolidays();
holidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = weekday.AddDays(1), EndDate = weekday.AddDays(10) });
var vm = BuildViewModel(slots, new FakeGroups([group]), holidays);
var cell = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 1);
Assert.Equal("1", cell.BadgeText);
}
[Fact]
public void Load_Wochenkachel_LetzteStundeVorFerien_ZeigtBadge1()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var holidays = new FakeSchoolHolidays();
holidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = date.AddDays(1), EndDate = date.AddDays(10) });
var vm = BuildViewModel(slots, new FakeGroups([group]), holidays);
var weekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.Equal("1", weekCell.HolidayBadge);
}
[Fact]
public void Load_VorletzteStundeVorFerien_ZeigtBadge2()
{
var firstOccurrence = NextGridWeekday(DateOnly.FromDateTime(DateTime.Today));
var secondOccurrence = firstOccurrence.AddDays(7);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = firstOccurrence.DayOfWeek, PeriodNumber = 1 });
var holidays = new FakeSchoolHolidays();
holidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = secondOccurrence.AddDays(1), EndDate = secondOccurrence.AddDays(10) });
var vm = BuildViewModel(slots, new FakeGroups([group]), holidays);
var cell = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == firstOccurrence.DayOfWeek && c.PeriodNumber == 1);
Assert.Equal("2", cell.BadgeText);
}
[Fact]
public void Load_Doppelstunde_BeideStundenZeigenDasselbeBadge()
{
var weekday = NextGridWeekday(DateOnly.FromDateTime(DateTime.Today));
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = weekday.DayOfWeek, PeriodNumber = 1 });
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = weekday.DayOfWeek, PeriodNumber = 2 });
var holidays = new FakeSchoolHolidays();
holidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = weekday.AddDays(1), EndDate = weekday.AddDays(10) });
var vm = BuildViewModel(slots, new FakeGroups([group]), holidays);
var period1 = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 1);
var period2 = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 2);
Assert.Equal("1", period1.BadgeText);
Assert.Equal("1", period2.BadgeText);
}
[Fact]
public void Load_KeineAnstehendenFerien_KeinBadge()
{
var weekday = NextGridWeekday(DateOnly.FromDateTime(DateTime.Today));
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = weekday.DayOfWeek, PeriodNumber = 1 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var cell = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 1);
Assert.False(cell.HasBadge);
}
// ── Aufsicht: wiederkehrend (Bearbeiten-Raster) ──────────────────────────
[Fact]
public void Load_AufsichtEingetragen_ZeigtSupervisionRowMitOrt()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var label = vm.Cells.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
Assert.NotNull(label);
var mondayCell = vm.Cells
.SkipWhile(c => c != label).Skip(1) // erste Zelle nach dem Label = Montag
.First();
Assert.True(mondayCell.IsSupervisionCell);
Assert.Equal("Pausenhof", mondayCell.SupervisionLocation);
}
[Fact]
public void Load_KeineAufsichtEingetragen_KeineSupervisionRow()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
Assert.DoesNotContain(vm.Cells, c => c.IsSupervisionRow);
}
[Fact]
public void Load_AufsichtEingetragen_BekommtSchmalereZeilenhoeheAlsStundenzeilen()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var row = vm.GridRows.Single(r => r.Cells.Any(c => c.IsSupervisionRow && c.Text.Contains("n. 2.")));
Assert.Equal(22, row.RowHeight);
Assert.Contains(vm.GridRows, r => r.RowHeight == 46); // normale Stundenzeilen bleiben unverändert
}
// ── Aufsicht + Vertretung im Wochenraster ────────────────────────────────
[Fact]
public void Load_Wochenraster_ZeigtWiederkehrendeAufsicht()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
Assert.Equal("Pausenhof", mondayCell.SupervisionLocation);
Assert.False(mondayCell.IsSubstitutionSupervision);
}
[Fact]
public void Load_Wochenraster_AufsichtBekommtSchmalereZeilenhoeheAlsStundenzeilen()
{
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var row = vm.WeekRows.Single(r => r.Cells.Any(c => c.IsSupervisionRow && c.Text.Contains("n. 2.")));
Assert.Equal(22, row.RowHeight);
Assert.Contains(vm.WeekRows, r => r.RowHeight == 76);
}
[Fact]
public void Load_Wochenraster_VertretungsaufsichtUeberschreibtRegulaereAnzeige()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.Supervision, AfterPeriod = 2,
Description = "Vertretung für Hr. Müller",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]),
supervisionDuties: duties, substitutions: substitutions);
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
Assert.Equal("Vertretung für Hr. Müller", mondayCell.SupervisionLocation);
Assert.True(mondayCell.IsSubstitutionSupervision);
}
[Fact]
public void Load_Wochenraster_VertretungsaufsichtOhneRegulaereAufsicht_ZeigtEigeneRow()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.Supervision, AfterPeriod = 3,
Description = "Vertretung für Fr. Schmidt",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 3."));
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
Assert.Equal("Vertretung für Fr. Schmidt", mondayCell.SupervisionLocation);
}
[Fact]
public void Load_Wochenraster_VertretungsstundeUeberschreibtNormaleAnzeige()
{
var group = new LearningGroup { Name = "Q1 Chemie" };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.Lesson, PeriodNumber = 1,
GroupLabel = "8a", Description = "Vertretung Erdkunde",
});
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.True(cell.IsSubstitutionLesson);
Assert.Equal("8a", cell.GroupName);
Assert.Equal("Vertretung Erdkunde", cell.Topic);
}
[Fact]
public void Load_Wochenraster_VertretungsstundeOhnePassendenSlot_ErscheintTrotzdem()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.Lesson, PeriodNumber = 5,
GroupLabel = "8a", Description = "Vertretung Erdkunde",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 5);
Assert.True(cell.IsSubstitutionLesson);
Assert.True(cell.IsAssigned);
}
// ── Aufsicht + Vertretung in der Tagesliste ("Heute") ────────────────────
[Fact]
public void Load_Heute_ZeigtHeutigeAufsicht()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var duties = new FakeSupervisionDuties();
duties.Add(new SupervisionDuty { Weekday = today.DayOfWeek, AfterPeriod = 2, Location = "Pausenhof" });
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
var item = Assert.Single(vm.TodaySupervisions);
Assert.Equal("Pausenhof", item.Description);
Assert.False(item.IsSubstitution);
}
[Fact]
public void Load_Heute_VertretungsstundeErsetztNormaleStundenanzeige()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = today, Kind = SubstitutionKind.Lesson, PeriodNumber = 1,
GroupLabel = "8a", Description = "Vertretung Erdkunde",
});
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
var item = Assert.Single(vm.TodayItems);
Assert.True(item.IsSubstitution);
Assert.Equal("8a", item.GroupName);
Assert.Equal("Vertretung Erdkunde", item.LessonTopic);
}
[Fact]
public void OpenGroup_MitLeererGroupId_RuftDelegateNichtAuf()
{
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
var called = false;
vm.OnNavigateToGroup = _ => called = true;
vm.OpenGroupCommand.Execute(Guid.Empty);
Assert.False(called);
}
// ── Sondereinsätze (Ausflüge, Berufsmessen, ...) ─────────────────────────
[Fact]
public void Load_Wochenraster_GanztaegigerSondereinsatz_UeberdecktAllePeriodenDesTages()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true,
GroupLabel = "8a", Description = "Ausflug ins Museum",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var period1 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
var period7 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 7);
Assert.True(period1.IsSpecialAssignment);
Assert.True(period7.IsSpecialAssignment);
Assert.Equal("Ausflug ins Museum", period1.Topic);
Assert.Equal("8a", period1.GroupName);
}
[Fact]
public void Load_Wochenraster_SondereinsatzMitStundenbereich_NurDortSichtbar()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = false,
FromPeriod = 3, ToPeriod = 4, Description = "Berufsmesse",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var period2 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
var period3 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 3);
var period4 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 4);
var period5 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 5);
Assert.False(period2.IsSpecialAssignment);
Assert.True(period3.IsSpecialAssignment);
Assert.True(period4.IsSpecialAssignment);
Assert.False(period5.IsSpecialAssignment);
}
[Fact]
public void Load_Wochenraster_SondereinsatzOhneGruppe_ZeigtLeeresGroupName()
{
var date = DateInCurrentWeek(DayOfWeek.Monday);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true, Description = "Berufsmesse",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
Assert.Equal("", cell.GroupName);
}
[Fact]
public void Load_Heute_ZeigtGanztaegigenSondereinsatz()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = today, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true,
GroupLabel = "8a", Description = "Ausflug ins Museum",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var item = Assert.Single(vm.TodaySpecialAssignments);
Assert.Equal("Ganztägig", item.PeriodLabel);
Assert.Equal("Ausflug ins Museum", item.Description);
Assert.Equal("8a", item.GroupLabel);
}
[Fact]
public void Load_Heute_ZeigtSondereinsatzMitStundenbereich()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = today, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = false,
FromPeriod = 3, ToPeriod = 4, Description = "Berufsmesse",
});
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
var item = Assert.Single(vm.TodaySpecialAssignments);
Assert.Equal("3.4. Stunde", item.PeriodLabel);
}
// ── Stundenausfall ────────────────────────────────────────────────────────
[Fact]
public void Load_Wochenraster_Ausfall_ZeigtGruppeUndFachAusDemStundenplan()
{
var subject = new Subject { Name = "Naturwissenschaften", ShortName = "NAT" };
var group = new LearningGroup { Name = "6a", SubjectId = subject.Id };
var date = DateInCurrentWeek(DayOfWeek.Monday);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = date, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2, Description = "6a auf Klassenfahrt",
});
var vm = BuildViewModel(slots, new FakeGroups([group]), subjects: new FakeSubjects([subject]), substitutions: substitutions);
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
Assert.True(cell.IsCancelled);
Assert.Equal("NAT", cell.SubjectLabel);
Assert.Equal("6a", cell.GroupName);
Assert.Equal("6a auf Klassenfahrt", cell.Topic);
}
[Fact]
public void Load_Wochenraster_KeinAusfall_ZeigtNormaleStunde()
{
var group = new LearningGroup { Name = "6a" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
var vm = BuildViewModel(slots, new FakeGroups([group]));
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
Assert.False(cell.IsCancelled);
}
[Fact]
public void Load_Heute_Ausfall_ErsetztNormaleStundenanzeige()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "6a" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 2 });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry
{
Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2, Description = "6a auf Klassenfahrt",
});
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
var item = Assert.Single(vm.TodayItems);
Assert.True(item.IsCancelled);
Assert.Equal("6a", item.GroupName);
Assert.Equal("6a auf Klassenfahrt", item.LessonTopic);
Assert.Equal(group.Id, item.GroupId);
}
[Fact]
public void Load_Heute_AusfallOhneGrund_LessonTopicBleibtLeer()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "6a" };
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 2 });
var substitutions = new FakeSubstitutionEntries();
substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2 });
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
var item = Assert.Single(vm.TodayItems);
Assert.False(item.HasLessonTopic);
}
// ── Anstehende Klausurtermine (4.4.3) ────────────────────────────────────
[Fact]
public void Load_ZeigtKlausurInnerhalbDesVorausblicks()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today.AddDays(10), Title = "Klausur Nr. 2" }]);
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([group]), exams: exams);
var item = Assert.Single(vm.UpcomingExams);
Assert.Equal("Q1 Chemie", item.GroupName);
Assert.Equal("Klausur Nr. 2", item.Title);
Assert.Equal(today.AddDays(10), item.Date);
}
[Fact]
public void Load_KlausurAusserhalbDesVorausblicks_WirdNichtGezeigt()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today.AddDays(30), Title = "Klausur Nr. 3" }]);
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([group]), exams: exams);
Assert.Empty(vm.UpcomingExams);
}
[Fact]
public void Load_VergangeneKlausur_WirdNichtGezeigt()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today.AddDays(-1), Title = "Alte Klausur" }]);
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([group]), exams: exams);
Assert.Empty(vm.UpcomingExams);
}
[Fact]
public void Load_MehrereKlausuren_SindNachDatumSortiert()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var exams = new FakeExams(
[
new Exam { GroupId = group.Id, Date = today.AddDays(15), Title = "Später" },
new Exam { GroupId = group.Id, Date = today.AddDays(3), Title = "Früher" },
]);
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([group]), exams: exams);
Assert.Equal(["Früher", "Später"], vm.UpcomingExams.Select(e => e.Title));
}
[Fact]
public void Load_HeutigeKlausur_IstImVorausblickEnthalten()
{
var today = DateOnly.FromDateTime(DateTime.Today);
var group = new LearningGroup { Name = "Q1 Chemie" };
var exams = new FakeExams([new Exam { GroupId = group.Id, Date = today, Title = "Heute" }]);
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([group]), exams: exams);
Assert.Single(vm.UpcomingExams);
}
}