Files
LehrerApp/LehrerApp.Desktop.Tests/PlanningTabViewModelTests.cs
T
2026-09-05 00:02:28 +02:00

377 lines
17 KiB
C#

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(), new FakeSessions([]));
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 void Initialize_WaehltLaufendeEinheitAlsArbeitskontext()
{
var (vm, units, _, groupId) = BuildScenario();
units.Add(new Unit { GroupId = groupId, Title = "Früher", Status = UnitStatus.Completed,
StartDate = new DateOnly(2025, 8, 1) });
var active = new Unit { GroupId = groupId, Title = "Aktuell", Status = UnitStatus.Active,
StartDate = new DateOnly(2025, 9, 1) };
units.Add(active);
vm.Initialize(groupId);
Assert.Equal(active.Id, vm.SelectedUnit?.Id);
}
[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<MoveLessonTarget?>(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<MoveLessonTarget?>(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 ChangeLessonUnit_OrdnetStundeDerZieleinheitZuUndWaehltSieAus()
{
var (vm, units, lessons, groupId) = BuildScenario();
var unitA = new Unit { GroupId = groupId, Title = "Mechanik" };
var unitB = new Unit { GroupId = groupId, Title = "Optik" };
units.Add(unitA); units.Add(unitB);
var l1 = new Lesson { UnitId = unitA.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Status = LessonStatus.Planned, Topic = "1" };
lessons.Add(l1);
vm.Initialize(groupId);
vm.SelectedUnit = vm.Units.Single(u => u.Id == unitA.Id);
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == l1.Id);
vm.OnPickUnitChange = _ => Task.FromResult<Unit?>(unitB);
await vm.ChangeLessonUnitCommand.ExecuteAsync(null);
Assert.Empty(lessons.GetByUnit(unitA.Id));
var moved = Assert.Single(lessons.GetByUnit(unitB.Id));
Assert.Equal(l1.Id, moved.Id);
Assert.Equal(unitB.Id, vm.SelectedUnit?.Id);
Assert.Equal(l1.Id, vm.SelectedLesson?.Id);
}
[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<CopyUnitTarget?>(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<LessonSeriesResult?>(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);
}
[Fact]
public void CreateParticipationSession_LegtSitzungMitDatumUndThemaDerStundeAn()
{
var groupId = Guid.NewGuid();
var group = new LearningGroup { Id = groupId, Name = "Testgruppe" };
var groups = new FakeGroups([group]);
var units = new FakeUnits();
var lessons = new FakeLessons();
var sessions = new FakeSessions([]);
var vm = new PlanningTabViewModel(units, lessons, groups, new FakeSubjects([]),
new FakeCompetencyDomains(), TestSupport.BuildAiSettingsService(), sessions);
vm.Initialize(groupId);
var unit = new Unit { GroupId = groupId, Title = "Optik" };
units.Add(unit);
var lesson = new Lesson
{
UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Topic = "Brechung",
};
lessons.Add(lesson);
vm.RefreshPlanning(unit.Id, lesson.Id);
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == lesson.Id);
string? notified = null;
vm.OnNotify = m => notified = m;
vm.CreateParticipationSessionCommand.Execute(null);
var created = Assert.Single(sessions.GetByGroup(groupId));
Assert.Equal(lesson.Id, created.LessonId);
Assert.Equal(lesson.Date, created.Date);
Assert.Equal("Brechung", created.Comment);
Assert.Equal("Sitzung aus der Stunde erstellt.", notified);
}
[Fact]
public void CreateParticipationSession_ZweiterAufrufLegtKeineDoppelteSitzungAn()
{
var groupId = Guid.NewGuid();
var group = new LearningGroup { Id = groupId, Name = "Testgruppe" };
var groups = new FakeGroups([group]);
var units = new FakeUnits();
var lessons = new FakeLessons();
var sessions = new FakeSessions([]);
var vm = new PlanningTabViewModel(units, lessons, groups, new FakeSubjects([]),
new FakeCompetencyDomains(), TestSupport.BuildAiSettingsService(), sessions);
vm.Initialize(groupId);
var unit = new Unit { GroupId = groupId, Title = "Optik" };
units.Add(unit);
var lesson = new Lesson
{
UnitId = unit.Id, GroupId = groupId, Date = new DateOnly(2025, 9, 1), Topic = "Brechung",
};
lessons.Add(lesson);
vm.RefreshPlanning(unit.Id, lesson.Id);
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == lesson.Id);
vm.CreateParticipationSessionCommand.Execute(null);
string? notified = null;
vm.OnNotify = m => notified = m;
vm.CreateParticipationSessionCommand.Execute(null);
Assert.Single(sessions.GetByGroup(groupId));
Assert.Equal("Für diese Stunde existiert bereits eine Sitzung.", notified);
}
[Fact]
public void CreateParticipationSession_AndereStundeAmSelbenTagBereitsVerknuepft_LegtKeineZweiteSitzungAn()
{
// Nutzer-Feedback: eine dritte Stunde am selben Tag (z.B. Vertretung) soll die bereits
// bestehende Sitzung des Tages weiterverwenden statt eine zweite anzulegen.
var groupId = Guid.NewGuid();
var group = new LearningGroup { Id = groupId, Name = "Testgruppe" };
var groups = new FakeGroups([group]);
var units = new FakeUnits();
var lessons = new FakeLessons();
var sessions = new FakeSessions([]);
var vm = new PlanningTabViewModel(units, lessons, groups, new FakeSubjects([]),
new FakeCompetencyDomains(), TestSupport.BuildAiSettingsService(), sessions);
vm.Initialize(groupId);
var unit = new Unit { GroupId = groupId, Title = "Optik" };
units.Add(unit);
var date = new DateOnly(2025, 9, 1);
var doubleLesson = new Lesson
{ UnitId = unit.Id, GroupId = groupId, Date = date, Topic = "Brechung" };
var thirdLesson = new Lesson
{ UnitId = unit.Id, GroupId = groupId, Date = date, Topic = "Vertretung" };
lessons.Add(doubleLesson);
lessons.Add(thirdLesson);
vm.RefreshPlanning(unit.Id, doubleLesson.Id);
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == doubleLesson.Id);
vm.CreateParticipationSessionCommand.Execute(null);
string? notified = null;
vm.OnNotify = m => notified = m;
vm.RefreshPlanning(unit.Id, thirdLesson.Id);
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == thirdLesson.Id);
vm.CreateParticipationSessionCommand.Execute(null);
var created = Assert.Single(sessions.GetByGroup(groupId));
Assert.Equal(doubleLesson.Id, created.LessonId);
Assert.Equal("Für diesen Tag existiert bereits eine Sitzung.", notified);
}
}