Stundenplan: Wochenraster, Wochennavigation, Ferien/Feiertage (Kapitel 4.3)
Neuer Stundenplan mit "Heute"-Standardansicht (Tagesliste unten angedockt, gruppenübergreifendes Wochenraster mit Fach/Klasse/Raum/Thema, Vor-/Zurück- Navigation zwischen Kalenderwochen) und separatem Bearbeiten-Raster für die wöchentliche Zuordnung. Badges für Ferien-/Klausur-Nähe und ausgegraute Ferientage direkt im Plan statt einer separaten Liste. Ferien-/Feiertage- Pflege (Bundesland, Schulferien) sitzt jetzt in den Einstellungen statt im Stundenplan selbst. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,17 @@ public class FakeResults : IExamResultRepository
|
||||
public void SaveMany(List<ExamResult> results) { }
|
||||
}
|
||||
|
||||
public class FakeGradingKeyTemplates : IGradingKeyTemplateRepository
|
||||
{
|
||||
private readonly List<GradingKeyTemplate> _all = [];
|
||||
public List<GradingKeyTemplate> GetAll() => _all;
|
||||
public List<GradingKeyTemplate> GetByGradingSystem(GradingSystem system) =>
|
||||
_all.Where(t => t.GradingSystem == system).ToList();
|
||||
public GradingKeyTemplate? GetById(Guid id) => _all.FirstOrDefault(t => t.Id == id);
|
||||
public void Save(GradingKeyTemplate template) { _all.RemoveAll(t => t.Id == template.Id); _all.Add(template); }
|
||||
public void Delete(Guid id) => _all.RemoveAll(t => t.Id == id);
|
||||
}
|
||||
|
||||
public class FakeSchemes : IGradingSchemeRepository
|
||||
{
|
||||
private readonly Dictionary<Guid, GradingScheme> _byGroup = [];
|
||||
@@ -206,6 +217,32 @@ public class FakeAlternativeLessonPaths(List<AlternativeLessonPath> all) : IAlte
|
||||
public void Delete(Guid id) => all.RemoveAll(p => p.Id == id);
|
||||
}
|
||||
|
||||
public class FakeTimetableSlots : ITimetableSlotRepository
|
||||
{
|
||||
private readonly List<TimetableSlot> _all = [];
|
||||
public void Add(TimetableSlot s) => _all.Add(s);
|
||||
public List<TimetableSlot> GetAll() => _all.ToList();
|
||||
public List<TimetableSlot> GetByGroup(Guid groupId) => _all.Where(s => s.GroupId == groupId).ToList();
|
||||
public void Save(TimetableSlot slot)
|
||||
{
|
||||
var occupied = _all.FirstOrDefault(s => s.Weekday == slot.Weekday && s.PeriodNumber == slot.PeriodNumber);
|
||||
if (occupied is not null && occupied.Id != slot.Id)
|
||||
throw new InvalidOperationException("Diese Stunde ist bereits belegt.");
|
||||
_all.RemoveAll(s => s.Id == slot.Id);
|
||||
_all.Add(slot);
|
||||
}
|
||||
public void Delete(Guid id) => _all.RemoveAll(s => s.Id == id);
|
||||
}
|
||||
|
||||
public class FakeSchoolHolidays : ISchoolHolidayRepository
|
||||
{
|
||||
private readonly List<SchoolHoliday> _all = [];
|
||||
public void Add(SchoolHoliday h) => _all.Add(h);
|
||||
public List<SchoolHoliday> GetAll() => _all.ToList();
|
||||
public void Save(SchoolHoliday holiday) { _all.RemoveAll(h => h.Id == holiday.Id); _all.Add(holiday); }
|
||||
public void Delete(Guid id) => _all.RemoveAll(h => h.Id == id);
|
||||
}
|
||||
|
||||
public class FakeReportGrades : IReportGradeRepository
|
||||
{
|
||||
private readonly List<ReportGrade> _all = [];
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Data;
|
||||
using LehrerApp.Desktop.ViewModels.Settings;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class SettingsViewModelTests
|
||||
{
|
||||
private static SettingsViewModel BuildViewModel(FakeSchoolHolidays? holidays = null)
|
||||
{
|
||||
// Bewusst kein "using": SchoolCalendarSettingsService liest den Pfad erst bei SetState,
|
||||
// das Verzeichnis muss über die Lebensdauer des ViewModels bestehen bleiben.
|
||||
var tempPath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-settingsvm-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
|
||||
return new SettingsViewModel(
|
||||
new FakeSubjects([]), new FakeCompetencyDomains(), new FakeGradingKeyTemplates(),
|
||||
new FakeSchemes(), new GradingService(), new BackupService(tempPath),
|
||||
new DatabaseEncryptionService(), new AppLockService(tempPath),
|
||||
new LiteDbContext(new MemoryStream()), new PrivacySettingsService(tempPath),
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
holidays ?? new FakeSchoolHolidays(), new SchoolCalendarSettingsService(tempPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchoolHoliday_GueltigeEingabe_WirdGespeichertUndInListeAngezeigt()
|
||||
{
|
||||
var holidays = new FakeSchoolHolidays();
|
||||
var vm = BuildViewModel(holidays);
|
||||
vm.NewHolidayName = "Sommerferien";
|
||||
vm.NewHolidayStartText = "01.07.2026";
|
||||
vm.NewHolidayEndText = "10.08.2026";
|
||||
|
||||
vm.AddSchoolHolidayCommand.Execute(null);
|
||||
|
||||
Assert.Single(holidays.GetAll());
|
||||
Assert.Single(vm.SchoolHolidayEntries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchoolHoliday_EndeVorBeginn_SetztFehlerUndSpeichertNicht()
|
||||
{
|
||||
var holidays = new FakeSchoolHolidays();
|
||||
var vm = BuildViewModel(holidays);
|
||||
vm.NewHolidayName = "Ungültig";
|
||||
vm.NewHolidayStartText = "10.08.2026";
|
||||
vm.NewHolidayEndText = "01.07.2026";
|
||||
|
||||
vm.AddSchoolHolidayCommand.Execute(null);
|
||||
|
||||
Assert.Empty(holidays.GetAll());
|
||||
Assert.NotEqual("", vm.HolidayDateError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddSchoolHoliday_FehlenderName_SetztFehlerUndSpeichertNicht()
|
||||
{
|
||||
var holidays = new FakeSchoolHolidays();
|
||||
var vm = BuildViewModel(holidays);
|
||||
vm.NewHolidayStartText = "01.07.2026";
|
||||
vm.NewHolidayEndText = "10.08.2026";
|
||||
|
||||
vm.AddSchoolHolidayCommand.Execute(null);
|
||||
|
||||
Assert.Empty(holidays.GetAll());
|
||||
Assert.NotEqual("", vm.HolidayNameError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveSchoolHoliday_EntferntEintragAusRepositoryUndListe()
|
||||
{
|
||||
var holidays = new FakeSchoolHolidays();
|
||||
var holiday = new SchoolHoliday { Name = "Herbstferien", StartDate = new DateOnly(2026, 10, 12), EndDate = new DateOnly(2026, 10, 24) };
|
||||
holidays.Add(holiday);
|
||||
var vm = BuildViewModel(holidays);
|
||||
|
||||
vm.RemoveSchoolHolidayCommand.Execute(vm.SchoolHolidayEntries[0]);
|
||||
|
||||
Assert.Empty(holidays.GetAll());
|
||||
Assert.Empty(vm.SchoolHolidayEntries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectedStateName_Aendern_PersistiertUeberSchoolCalendarSettings()
|
||||
{
|
||||
var tempPath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-settingsvm-state-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var calendarSettings = new SchoolCalendarSettingsService(tempPath);
|
||||
|
||||
var vm = new SettingsViewModel(
|
||||
new FakeSubjects([]), new FakeCompetencyDomains(), new FakeGradingKeyTemplates(),
|
||||
new FakeSchemes(), new GradingService(), new BackupService(tempPath),
|
||||
new DatabaseEncryptionService(), new AppLockService(tempPath),
|
||||
new LiteDbContext(new MemoryStream()), new PrivacySettingsService(tempPath),
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
new FakeSchoolHolidays(), calendarSettings);
|
||||
|
||||
vm.SelectedStateName = "Bayern";
|
||||
|
||||
Assert.Equal(GermanState.BY, calendarSettings.State);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class TimetableSlotDialogViewModelTests
|
||||
{
|
||||
private static SchoolYearService SchoolYear() => new();
|
||||
|
||||
[Fact]
|
||||
public void Save_OhneAusgewaehlteGruppe_SetztFehlerUndSpeichertNicht()
|
||||
{
|
||||
var slots = new FakeTimetableSlots();
|
||||
var groups = new FakeGroups([]);
|
||||
var vm = new TimetableSlotDialogViewModel(slots, groups, SchoolYear(), DayOfWeek.Monday, 1, null);
|
||||
|
||||
vm.SaveCommand.Execute(null);
|
||||
|
||||
Assert.Null(vm.Result);
|
||||
Assert.NotEqual("", vm.GroupError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_MitAusgewaehlterGruppe_LegtNeuenSlotAn()
|
||||
{
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
var groups = new FakeGroups([group]);
|
||||
var vm = new TimetableSlotDialogViewModel(slots, groups, SchoolYear(), DayOfWeek.Monday, 1, null)
|
||||
{
|
||||
SelectedGroupName = "Q1 Chemie", Room = "R204",
|
||||
};
|
||||
|
||||
vm.SaveCommand.Execute(null);
|
||||
|
||||
Assert.NotNull(vm.Result);
|
||||
Assert.Equal(group.Id, vm.Result!.GroupId);
|
||||
Assert.Equal("R204", vm.Result.Room);
|
||||
Assert.Single(slots.GetAll());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_BelegteStunde_ZeigtFreundlicheFehlermeldungStattAbsturz()
|
||||
{
|
||||
var groupA = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var groupB = new LearningGroup { Name = "Q1 Physik" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = groupA.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
var groups = new FakeGroups([groupA, groupB]);
|
||||
var vm = new TimetableSlotDialogViewModel(slots, groups, SchoolYear(), DayOfWeek.Monday, 1, null)
|
||||
{
|
||||
SelectedGroupName = "Q1 Physik",
|
||||
};
|
||||
|
||||
vm.SaveCommand.Execute(null);
|
||||
|
||||
Assert.Null(vm.Result);
|
||||
Assert.Equal("Diese Stunde ist bereits belegt.", vm.GroupError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_BeimBearbeitenEinesVorhandenenSlots_EntferntIhn()
|
||||
{
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var slot = new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(slot);
|
||||
var groups = new FakeGroups([group]);
|
||||
var vm = new TimetableSlotDialogViewModel(slots, groups, SchoolYear(), DayOfWeek.Monday, 1, slot);
|
||||
|
||||
vm.DeleteCommand.Execute(null);
|
||||
|
||||
Assert.True(vm.Deleted);
|
||||
Assert.Empty(slots.GetAll());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
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)
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// 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 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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowEditor_SchaltetAufBearbeiten_Tab()
|
||||
{
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
|
||||
|
||||
vm.ShowEditorCommand.Execute(null);
|
||||
|
||||
Assert.Equal(1, vm.ActiveTabIndex);
|
||||
}
|
||||
|
||||
// ── "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_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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user