Files
LehrerApp/LehrerApp.Desktop.Tests/PlanningTabViewModelTests.cs
T
adminandClaude Sonnet 5 de6ea001e7 Unterrichtsplanung: Einheiten, Verlaufsplan-Editor, Kürzel-Katalog (Kapitel 4.1/4.2)
Neuer Tab "Planung" in GroupDetailView ersetzt den Platzhalter: Einheiten anlegen/bearbeiten/
als Vorlage in andere Gruppe kopieren, Stunden je Einheit mit Verschieben (inkl. Nachrücken
der Folgestunden). Stundeneditor als tabellarischer Verlaufsplan (Phase/Dauer/Tätigkeit/
Material/Kurzsymbol je Zeile, Uhrzeit aus optionalem Stundenbeginn abgeleitet) statt eines
einzelnen Phase-Felds mit Methoden-/Materialien-Chips — Kurzsymbol als Freitext mit
Vorschlägen aus neuem Kürzel-Katalog (Einstellungen) plus bisher verwendeten Werten.
Schema-Migrationen v1-v3 überführen bestehende Daten verlustfrei. 4.2.5 bewusst offen
gelassen (hängt an Stundenplan, Kapitel 4.3).

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

204 lines
9.6 KiB
C#

using LehrerApp.Core.Models;
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);
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<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 CopyUnit_ErhaeltAbstaendeUndSetztGroupIdAufZielgruppe()
{
var (vm, units, lessons, sourceGroupId) = BuildScenario();
var targetGroupId = Guid.NewGuid();
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" }],
};
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.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 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);
}
}