using LehrerApp.Core.Models; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.Groups; using Xunit; namespace LehrerApp.Desktop.Tests; /// Tests für die Unterrichtsplanung (4.1 Einheiten / 4.2 Einzelstunden): Fortschrittsberechnung, /// Verschieben mit/ohne Nachrücken der Folgestunden und die Vorlage-Kopie in eine andere Gruppe. public class PlanningTabViewModelTests { private static (PlanningTabViewModel Vm, FakeUnits Units, FakeLessons Lessons, Guid GroupId) BuildScenario() { var groupId = Guid.NewGuid(); var group = new LearningGroup { Id = groupId, Name = "Testgruppe" }; var groups = new FakeGroups([group]); var subjects = new FakeSubjects([]); var competencyDomains = new FakeCompetencyDomains(); var units = new FakeUnits(); var lessons = new FakeLessons(); var vm = new PlanningTabViewModel(units, lessons, groups, subjects, competencyDomains, TestSupport.BuildAiSettingsService()); vm.Initialize(groupId); return (vm, units, lessons, groupId); } [Fact] public void LoadUnits_BerechnetFortschrittAusDurchgefuehrtenStunden() { var (vm, units, lessons, groupId) = BuildScenario(); var unit = new Unit { GroupId = groupId, Title = "Optik" }; units.Add(unit); lessons.Add(new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Status = LessonStatus.Conducted, Topic = "A" }); lessons.Add(new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 8), Status = LessonStatus.Conducted, Topic = "B" }); lessons.Add(new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 15), Status = LessonStatus.Planned, Topic = "C" }); vm.Initialize(groupId); var summary = vm.Units.Single(u => u.Id == unit.Id); Assert.Equal(2, summary.ConductedCount); Assert.Equal(3, summary.TotalCount); Assert.Equal(2.0 / 3, summary.ProgressFraction, 3); Assert.Contains("2 / 3", summary.ProgressText); } [Fact] public async Task MoveLesson_MitNachrueckenVerschiebtNurGeplanteFolgestundenNachDemStichtag() { var (vm, units, lessons, groupId) = BuildScenario(); var unit = new Unit { GroupId = groupId, Title = "Mechanik" }; units.Add(unit); var l1 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Status = LessonStatus.Planned, Topic = "1" }; var l2 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 8), Status = LessonStatus.Planned, Topic = "2" }; var l3 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 15), Status = LessonStatus.Planned, Topic = "3" }; var l4 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 22), Status = LessonStatus.Conducted, Topic = "4" }; lessons.Add(l1); lessons.Add(l2); lessons.Add(l3); lessons.Add(l4); vm.Initialize(groupId); vm.SelectedUnit = vm.Units.Single(u => u.Id == unit.Id); vm.SelectedLesson = vm.Lessons.Single(l => l.Id == l1.Id); vm.OnPickMoveTarget = _ => Task.FromResult(new MoveLessonTarget(new DateOnly(2025, 9, 3), true)); await vm.MoveLessonCommand.ExecuteAsync(null); var byUnit = lessons.GetByUnit(unit.Id).ToDictionary(l => l.Id); Assert.Equal(new DateOnly(2025, 9, 3), byUnit[l1.Id].Date); Assert.Equal(new DateOnly(2025, 9, 10), byUnit[l2.Id].Date); // +2 Tage nachgerückt Assert.Equal(new DateOnly(2025, 9, 17), byUnit[l3.Id].Date); // +2 Tage nachgerückt Assert.Equal(new DateOnly(2025, 9, 22), byUnit[l4.Id].Date); // Conducted: unverändert } [Fact] public async Task MoveLesson_OhneNachrueckenVerschiebtNurDieAusgewaehlteStunde() { var (vm, units, lessons, groupId) = BuildScenario(); var unit = new Unit { GroupId = groupId, Title = "Mechanik" }; units.Add(unit); var l1 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Status = LessonStatus.Planned, Topic = "1" }; var l2 = new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 8), Status = LessonStatus.Planned, Topic = "2" }; lessons.Add(l1); lessons.Add(l2); vm.Initialize(groupId); vm.SelectedUnit = vm.Units.Single(u => u.Id == unit.Id); vm.SelectedLesson = vm.Lessons.Single(l => l.Id == l1.Id); vm.OnPickMoveTarget = _ => Task.FromResult(new MoveLessonTarget(new DateOnly(2025, 9, 5), false)); await vm.MoveLessonCommand.ExecuteAsync(null); var byUnit = lessons.GetByUnit(unit.Id).ToDictionary(l => l.Id); Assert.Equal(new DateOnly(2025, 9, 5), byUnit[l1.Id].Date); Assert.Equal(new DateOnly(2025, 9, 8), byUnit[l2.Id].Date); // unverändert } [Fact] public async Task CopyUnit_ErhaeltAbstaendeUndSetztGroupIdAufZielgruppe() { var (vm, units, lessons, sourceGroupId) = BuildScenario(); var targetGroupId = Guid.NewGuid(); var kurzversionId = Guid.NewGuid(); // Katalog-Referenz, bleibt beim Kopieren unverändert var unit = new Unit { GroupId = sourceGroupId, Title = "Kinetik", StartDate = new DateOnly(2025, 9, 1), EndDate = new DateOnly(2025, 10, 1), Notes = "Original-Notiz", }; units.Add(unit); var l1 = new Lesson { UnitId = unit.Id, GroupId = sourceGroupId, Date = new DateOnly(2025, 9, 1), Topic = "Einführung", Status = LessonStatus.Conducted, Reflection = "lief gut", StartTime = new TimeOnly(11, 45), Phases = [new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 10, Material = "AB01", Shorthand = "L->AB01", AlternativePathId = kurzversionId }], }; var l2 = new Lesson { UnitId = unit.Id, GroupId = sourceGroupId, Date = new DateOnly(2025, 9, 8), Topic = "Vertiefung", Status = LessonStatus.Planned, }; lessons.Add(l1); lessons.Add(l2); vm.Initialize(sourceGroupId); vm.SelectedUnit = vm.Units.Single(u => u.Id == unit.Id); var anchor = new DateOnly(2025, 10, 1); vm.OnPickCopyTarget = _ => Task.FromResult(new CopyUnitTarget(targetGroupId, anchor)); await vm.CopyUnitCommand.ExecuteAsync(null); var newUnit = units.GetByGroup(targetGroupId).Single(); Assert.Equal("Kinetik", newUnit.Title); Assert.Equal(UnitStatus.Planned, newUnit.Status); Assert.Equal(anchor, newUnit.StartDate); Assert.Equal(anchor.AddDays(30), newUnit.EndDate); // ursprünglich 30 Tage nach StartDate var copiedLessons = lessons.GetByUnit(newUnit.Id).OrderBy(l => l.Date).ToList(); Assert.Equal(2, copiedLessons.Count); Assert.All(copiedLessons, l => Assert.Equal(targetGroupId, l.GroupId)); // nicht sourceGroupId Assert.Equal(anchor, copiedLessons[0].Date); Assert.Equal(anchor.AddDays(7), copiedLessons[1].Date); // 7-Tage-Abstand erhalten Assert.All(copiedLessons, l => Assert.Equal(LessonStatus.Planned, l.Status)); Assert.Null(copiedLessons[0].Reflection); // Reflexion wird bei der Kopie geleert Assert.Equal(new TimeOnly(11, 45), copiedLessons[0].StartTime); // Beginn wird übernommen Assert.Single(copiedLessons[0].Phases); Assert.Equal("Einstieg", copiedLessons[0].Phases[0].Name); Assert.Equal("L->AB01", copiedLessons[0].Phases[0].Shorthand); Assert.Equal(kurzversionId, copiedLessons[0].Phases[0].AlternativePathId); Assert.NotEqual(l1.Phases[0].Id, copiedLessons[0].Phases[0].Id); // frische Id, keine geteilte Referenz // Ursprüngliche Einheit/Stunden bleiben unverändert in der Quellgruppe. Assert.Single(units.GetByGroup(sourceGroupId)); Assert.Equal(LessonStatus.Conducted, lessons.GetByUnit(unit.Id).Single(l => l.Id == l1.Id).Status); } [Fact] public void LoadUnits_SammeltMaterialienAusAllenStundenphasenDerGruppe() { var (vm, units, lessons, groupId) = BuildScenario(); var unit1 = new Unit { GroupId = groupId, Title = "Einheit 1" }; var unit2 = new Unit { GroupId = groupId, Title = "Einheit 2" }; units.Add(unit1); units.Add(unit2); lessons.Add(new Lesson { UnitId = unit1.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Topic = "A", Phases = [new LessonPhaseStep { Material = "Arbeitsblatt" }], }); lessons.Add(new Lesson { UnitId = unit2.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 2), Topic = "B", Phases = [ new LessonPhaseStep { Material = "Modell" }, new LessonPhaseStep { Material = "Arbeitsblatt" }, // Duplikat, soll nur einmal erscheinen new LessonPhaseStep { Material = "" }, // leer, soll ignoriert werden ], }); vm.Initialize(groupId); Assert.Equal(["Arbeitsblatt", "Modell"], vm.KnownMaterials); } [Fact] public async Task GenerateLessonSeries_RuftDelegateMitAusgewaehlterEinheitAufUndLaedtBeiErfolgNeu() { var (vm, units, lessons, groupId) = BuildScenario(); var unit = new Unit { GroupId = groupId, Title = "Optik" }; units.Add(unit); vm.Initialize(groupId); vm.SelectedUnit = vm.Units.Single(u => u.Id == unit.Id); Unit? passedUnit = null; vm.OnGenerateLessonSeries = u => { passedUnit = u; lessons.Add(new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Topic = "" }); return Task.FromResult(new LessonSeriesResult(1, 0, 0)); }; await vm.GenerateLessonSeriesCommand.ExecuteAsync(null); Assert.Equal(unit.Id, passedUnit!.Id); Assert.Single(vm.Lessons); } [Fact] public void LoadUnits_SammeltKurzsymboleAusAllenStundenphasenDerGruppe() { var (vm, units, lessons, groupId) = BuildScenario(); var unit = new Unit { GroupId = groupId, Title = "Einheit 1" }; units.Add(unit); lessons.Add(new Lesson { UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Topic = "A", Phases = [ new LessonPhaseStep { Shorthand = "Plenum" }, new LessonPhaseStep { Shorthand = "L->AB01" }, new LessonPhaseStep { Shorthand = "" }, // leer, soll ignoriert werden ], }); vm.Initialize(groupId); Assert.Equal(["L->AB01", "Plenum"], vm.KnownShorthands); } }