Nutzer-Feedback: die Tagesliste-Buttons waren schon in Ordnung, das Problem
lag im Wochenraster darüber — ein Klick auf eine Stunden-Kachel führt dort
entweder in den Planungsviewer oder zur Einheitenplanung, ohne dass von
außen erkennbar wäre welches Ziel man bekommt. Tagesliste auf den
ursprünglichen Stand zurückgesetzt.
Neu: ein kleiner "⋮"-Button pro Kachel öffnet ein Popup-Menü mit vier
ausdrücklich benannten Zielen (Unterrichtsansicht/Planungsviewer/Sitzplan/
Planung). MenuFlyout statt ComboBox — dabei verstanden, dass ein
$parent[ItemsControl]-Vorfahrenpfad im Flyout nicht funktioniert, eine
normale {Binding} über die DataContext-Vererbung aber sehr wohl. Der
Direktklick springt jetzt außerdem "einheitlicher": bei einer Lesson
während der eigentlichen Unterrichtszeit direkt in den Unterrichtsmodus
statt in den Viewer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1296 lines
61 KiB
C#
1296 lines
61 KiB
C#
using LehrerApp.Core.Models;
|
||
using LehrerApp.Core.Services;
|
||
using LehrerApp.Desktop.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,
|
||
FakeUntisSlotMappings? untisMappings = null, WebUntisSettingsService? untisSettings = null,
|
||
PeriodScheduleService? periodSchedule = null)
|
||
{
|
||
// Bewusst kein "using": SchoolCalendarSettingsService/PeriodScheduleService lesen den Pfad
|
||
// nur bei Bedarf, 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(),
|
||
untisMappings ?? new FakeUntisSlotMappings(), untisSettings ?? TestSupport.BuildWebUntisSettingsService(),
|
||
periodSchedule ?? new PeriodScheduleService(tempPath));
|
||
}
|
||
|
||
/// 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);
|
||
}
|
||
|
||
/// Nutzer-Feedback (zweite Runde): der Direktklick auf eine Wochenraster-Kachel soll
|
||
/// "einheitlicher" springen — bei einer Lesson HEUTE, während gerade Unterrichtszeit ist,
|
||
/// direkt in den Unterrichtsmodus statt in den (schreibgeschützten) Planungsviewer.
|
||
[Fact]
|
||
public async Task OpenWeekCell_LessonHeuteWaehrendUnterrichtszeit_OeffnetUnterrichtsmodus()
|
||
{
|
||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||
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 periodSchedule = BuildPeriodSchedule(1);
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons, periodSchedule: periodSchedule);
|
||
Lesson? teachingModeLesson = null;
|
||
Lesson? viewerLesson = null;
|
||
vm.OnOpenTeachingMode = l => { teachingModeLesson = l; return Task.CompletedTask; };
|
||
vm.OnOpenLessonViewer = l => { viewerLesson = l; return Task.CompletedTask; };
|
||
|
||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == today.DayOfWeek && c.PeriodNumber == 1);
|
||
await vm.OpenWeekCellCommand.ExecuteAsync(cell);
|
||
|
||
Assert.Equal(lesson.Id, teachingModeLesson?.Id);
|
||
Assert.Null(viewerLesson);
|
||
}
|
||
|
||
/// Dieselbe Stundenzeit passt, die Lesson liegt aber nicht heute — bleibt beim Viewer.
|
||
[Fact]
|
||
public async Task OpenWeekCell_LessonNichtHeuteTrotzPassenderUhrzeit_OeffnetViewer()
|
||
{
|
||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||
var otherWeekday = today.DayOfWeek == DayOfWeek.Monday ? DayOfWeek.Tuesday : DayOfWeek.Monday;
|
||
var otherDate = DateInCurrentWeek(otherWeekday);
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = otherWeekday, PeriodNumber = 1 });
|
||
var lesson = new Lesson { GroupId = group.Id, Date = otherDate, LessonNumber = 1, Topic = "Redox" };
|
||
var lessons = new FakeLessons();
|
||
lessons.Add(lesson);
|
||
var periodSchedule = BuildPeriodSchedule(1);
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), lessons: lessons, periodSchedule: periodSchedule);
|
||
Lesson? teachingModeLesson = null;
|
||
Lesson? viewerLesson = null;
|
||
vm.OnOpenTeachingMode = l => { teachingModeLesson = l; return Task.CompletedTask; };
|
||
vm.OnOpenLessonViewer = l => { viewerLesson = l; return Task.CompletedTask; };
|
||
|
||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == otherWeekday && c.PeriodNumber == 1);
|
||
await vm.OpenWeekCellCommand.ExecuteAsync(cell);
|
||
|
||
Assert.Equal(lesson.Id, viewerLesson?.Id);
|
||
Assert.Null(teachingModeLesson);
|
||
}
|
||
|
||
/// Weites Zeitfenster um <paramref name="around"/> herum (±2 Stunden), damit der Test nicht an
|
||
/// die exakte Toleranz von TimetableViewModel.TeachingTimeTolerance gebunden ist.
|
||
/// Start UND Ende exakt auf "jetzt" statt eines künstlich breiteren Fensters (z.B. ganzer Tag
|
||
/// via TimeOnly.MinValue/MaxValue): TimetableViewModel.IsAroundTeachingTime addiert selbst
|
||
/// noch ±10 Minuten Toleranz (TeachingTimeTolerance) auf Start/Ende — ein zusätzliches, vom
|
||
/// Test vorgegebenes Fenster würde nahe Mitternacht durch die TimeOnly-Arithmetik (wrappt bei
|
||
/// 24 Uhr) selbst kippen. "Jetzt" als Start/Ende bleibt nur dann riskant, wenn der Test
|
||
/// zufällig innerhalb der letzten/ersten 10 Minuten des Tages läuft — dasselbe inhärente
|
||
/// Randproblem hätte dann auch die Produktion, kein Testartefakt.
|
||
private static PeriodScheduleService BuildPeriodSchedule(int periodNumber)
|
||
{
|
||
var tempPath = System.IO.Path.Combine(
|
||
System.IO.Path.GetTempPath(), $"lehrerapp-periodschedule-tests-{Guid.NewGuid():N}");
|
||
Directory.CreateDirectory(tempPath);
|
||
var service = new PeriodScheduleService(tempPath);
|
||
var now = TimeOnly.FromDateTime(DateTime.Now);
|
||
service.SetPeriods([new PeriodTimeEntry { PeriodNumber = periodNumber, Start = now, End = now }]);
|
||
return service;
|
||
}
|
||
|
||
[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_ZusaetzlicheAufsichtInDerFolgewoche_ErscheintImWochenrasterNachNavigation()
|
||
{
|
||
// Nutzer-Feedback: "Auch die Extra-Aufsicht ist dann nicht im Plan [...] So macht doch der
|
||
// Sync nur so halb Sinn" - dieselbe Anzeige-Infrastruktur wie beim Ausfall oben, hier für
|
||
// eine automatisch erkannte zusätzliche Aufsicht ohne reguläre SupervisionDuty.
|
||
var dateNextWeek = DateInCurrentWeek(DayOfWeek.Monday).AddDays(7);
|
||
var substitutions = new FakeSubstitutionEntries();
|
||
substitutions.Add(new SubstitutionEntry
|
||
{
|
||
Date = dateNextWeek, Kind = SubstitutionKind.Supervision, AfterPeriod = 1,
|
||
Description = "Automatisch über WebUntis-Abgleich erkannt: Zusätzliche Aufsicht laut WebUntis.",
|
||
});
|
||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||
|
||
Assert.DoesNotContain(vm.WeekItems, c => c.IsSupervisionRow && c.Text.Contains("n. 1."));
|
||
|
||
vm.NextWeekCommand.Execute(null);
|
||
|
||
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 1."));
|
||
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
|
||
Assert.True(mondayCell.IsSubstitutionSupervision);
|
||
Assert.Contains("Zusätzliche Aufsicht", 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_AusfallInDerFolgewoche_ErscheintImWochenrasterNachNavigation()
|
||
{
|
||
// Nutzer-Feedback: "Nächste Woche habe ich eine Stunde Ausfall [...] wäre es doch auch
|
||
// schön, wenn das im Stundenplan für die nächste Woche irgendwie erkenntlich ist" - genau
|
||
// der Weg, den ein automatisch von WebUntis erkannter Ausfall nimmt (derselbe
|
||
// SubstitutionEntry-Mechanismus wie bei Handeinträgen), hier erstmals mit WeekOffset != 0
|
||
// geprüft.
|
||
var subject = new Subject { Name = "Naturwissenschaften", ShortName = "NAT" };
|
||
var group = new LearningGroup { Name = "6a", SubjectId = subject.Id };
|
||
var dateNextWeek = DateInCurrentWeek(DayOfWeek.Thursday).AddDays(7);
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Thursday, PeriodNumber = 3 });
|
||
var substitutions = new FakeSubstitutionEntries();
|
||
substitutions.Add(new SubstitutionEntry
|
||
{
|
||
Date = dateNextWeek, Kind = SubstitutionKind.Cancelled, PeriodNumber = 3,
|
||
Description = "Automatisch über WebUntis-Abgleich erkannt.",
|
||
});
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), subjects: new FakeSubjects([subject]), substitutions: substitutions);
|
||
|
||
// Aktuelle Woche: an dieser Stelle regulär, noch kein Ausfall sichtbar.
|
||
var currentWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Thursday && c.PeriodNumber == 3);
|
||
Assert.False(currentWeekCell.IsCancelled);
|
||
|
||
vm.NextWeekCommand.Execute(null);
|
||
|
||
var nextWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Thursday && c.PeriodNumber == 3);
|
||
Assert.True(nextWeekCell.IsCancelled);
|
||
Assert.Equal("Automatisch über WebUntis-Abgleich erkannt.", nextWeekCell.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);
|
||
}
|
||
|
||
// ── WebUntis-Abweichung ───────────────────────────────────────────────────
|
||
|
||
[Fact]
|
||
public void Load_UntisAbgleichNichtAktiviert_ZeigtKeineAbweichung()
|
||
{
|
||
var group = new LearningGroup { Name = "10c" };
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]));
|
||
|
||
Assert.False(vm.HasUntisMismatch);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_UntisAktivSlotOhneBestaetigteZuordnung_ZeigtAbweichung()
|
||
{
|
||
var group = new LearningGroup { Name = "10c" };
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||
untisSettings.SetEnabled(true);
|
||
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisSettings: untisSettings);
|
||
|
||
Assert.True(vm.HasUntisMismatch);
|
||
Assert.Contains("1 Stundenplan-Eintrag", vm.UntisMismatchLabel);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_UntisAktivMitBestaetigterZuordnung_ZeigtKeineAbweichung()
|
||
{
|
||
var group = new LearningGroup { Name = "10c" };
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||
var mappings = new FakeUntisSlotMappings();
|
||
mappings.Add(new UntisSlotMapping
|
||
{
|
||
Weekday = DayOfWeek.Monday, PeriodNumber = 1, GroupId = group.Id, Confirmed = true,
|
||
});
|
||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||
untisSettings.SetEnabled(true);
|
||
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisMappings: mappings, untisSettings: untisSettings);
|
||
|
||
Assert.False(vm.HasUntisMismatch);
|
||
}
|
||
|
||
[Fact]
|
||
public void Load_DoppelstundenZuordnungBestaetigtBeideTimetableSlots()
|
||
{
|
||
// Regression: "ich habe aber jetzt alles zugeordnet, und trotzdem erhalte ich die
|
||
// Warnung, dass 16 Stunden ohne Untis-Zuordnung sind" - eine WebUntis-Doppelstunde
|
||
// bestätigt beide zugehörigen TimetableSlots, nicht nur den ersten.
|
||
var group = new LearningGroup { Name = "10c" };
|
||
var slots = new FakeTimetableSlots();
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
|
||
var mappings = new FakeUntisSlotMappings();
|
||
mappings.Add(new UntisSlotMapping
|
||
{
|
||
Weekday = DayOfWeek.Monday, PeriodNumber = 1, CoveredPeriods = [1, 2],
|
||
GroupId = group.Id, Confirmed = true,
|
||
});
|
||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||
untisSettings.SetEnabled(true);
|
||
|
||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisMappings: mappings, untisSettings: untisSettings);
|
||
|
||
Assert.False(vm.HasUntisMismatch);
|
||
}
|
||
}
|
||
|
||
/// Nutzer-Feedback (zweite Runde): das Popup-Menü je Wochenraster-Kachel (TimetableView.axaml,
|
||
/// MenuFlyout) blendet "Unterrichtsansicht"/"Planungsviewer" per IsVisible="{Binding HasLesson}"
|
||
/// aus, wenn für den Slot noch keine Lesson existiert, und den ganzen Menü-Trigger per
|
||
/// IsVisible="{Binding HasGroupId}", wenn die Kachel gar keiner Gruppe zugeordnet ist (z.B. eine
|
||
/// GroupId-lose Vertretung). Diese beiden Properties sind die Grundlage dafür.
|
||
public sealed class WeekCellItemMenuVisibilityTests
|
||
{
|
||
[Fact]
|
||
public void ForSlot_MitLesson_HatHasLessonUndHasGroupId()
|
||
{
|
||
var lesson = new Lesson { Topic = "Redox" };
|
||
var cell = WeekCellItem.ForSlot(DayOfWeek.Monday, 3, isToday: true, "Che", "Q1 Chemie",
|
||
"R204", "Redox", "#4C8DFF", "", hasExam: false, isLastBeforeExam: false,
|
||
hasExperiment: false, Guid.NewGuid(), isHoliday: false, lesson: lesson);
|
||
|
||
Assert.True(cell.HasLesson);
|
||
Assert.True(cell.HasGroupId);
|
||
}
|
||
|
||
[Fact]
|
||
public void ForSlot_OhneLesson_HatKeinHasLesson()
|
||
{
|
||
var cell = WeekCellItem.ForSlot(DayOfWeek.Monday, 3, isToday: true, "Che", "Q1 Chemie",
|
||
"R204", "", "#4C8DFF", "", hasExam: false, isLastBeforeExam: false,
|
||
hasExperiment: false, Guid.NewGuid(), isHoliday: false);
|
||
|
||
Assert.False(cell.HasLesson);
|
||
Assert.True(cell.HasGroupId);
|
||
}
|
||
|
||
[Fact]
|
||
public void ForSubstitutionLesson_OhneGroupId_HatKeinHasGroupId()
|
||
{
|
||
var cell = WeekCellItem.ForSubstitutionLesson(DayOfWeek.Monday, 3, isToday: true,
|
||
new SubstitutionEntry { GroupId = null, GroupLabel = "Fremde Klasse", Description = "Vertretung" });
|
||
|
||
Assert.False(cell.HasGroupId);
|
||
}
|
||
}
|