Stundenverlaufsplan: Viewer und Alternativpfad-Katalog (Kapitel 4.2 Nachtrag)

Schreibgeschützter LessonViewerDialog (größere Schrift, ohne Bearbeitungs-/Verlängern-/
Verschieben-Funktion) für den Einsatz während des Unterrichtens, erreichbar über "Anzeigen"
im Stunden-Toolbar.

Alternative Unterrichtsabläufe (z.B. Kurzversion bei Zeitmangel) laufen jetzt über einen
echten Katalog (neues Modell AlternativeLessonPath: Name + Beschreibung) statt Freitext direkt
an der Phase: im Verlaufsplan-Editor eine kompakte, farbig unterstützte Checkbox statt einer
durchgehend sichtbaren Eingabespalte, Zuordnung/Neuanlage über einen eigenen Dialog. Der Viewer
gruppiert Phasen entsprechend und zeigt die hinterlegte Beschreibung. Schema-Migration v3→v4
führt bestehende Freitextwerte verlustfrei in Katalogeinträge über.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 01:53:12 +02:00
co-authored by Claude Sonnet 5
parent de6ea001e7
commit ee1a47641e
22 changed files with 904 additions and 33 deletions
@@ -0,0 +1,100 @@
using LehrerApp.Core.Models;
using LehrerApp.Desktop.ViewModels.Groups;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class LessonViewerViewModelTests
{
[Fact]
public void OhneAlternativenGibtEsGenauEineHauptwegGruppe()
{
var lesson = new Lesson
{
Topic = "Brechung", Date = new DateOnly(2025, 9, 1), StartTime = new TimeOnly(11, 45),
Phases =
[
new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 5, Activity = "Begrüßung" },
new LessonPhaseStep { Name = "Erarbeitung", DurationMinutes = 15, Activity = "AB01 bearbeiten" },
],
};
var vm = new LessonViewerViewModel(lesson, new FakeAlternativeLessonPaths([]));
Assert.False(vm.HasAlternatives);
var group = Assert.Single(vm.PhaseGroups);
Assert.Equal("Hauptweg", group.Label);
Assert.True(group.IsMainPath);
Assert.Equal(2, group.Phases.Count);
Assert.Equal("ab 11:45", group.Phases[0].TimeDisplay);
Assert.Equal("ab 11:50", group.Phases[1].TimeDisplay);
}
[Fact]
public void OhneBeginnBleibtDieUhrzeitanzeigeLeer()
{
var lesson = new Lesson
{
Topic = "Brechung", Date = new DateOnly(2025, 9, 1),
Phases = [new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 5 }],
};
var vm = new LessonViewerViewModel(lesson, new FakeAlternativeLessonPaths([]));
Assert.Equal("", vm.PhaseGroups[0].Phases[0].TimeDisplay);
Assert.Equal("", vm.StartTimeDisplay);
}
[Fact]
public void GruppiertPhasenNachAlternativePathUndHauptwegStehtImmerZuerst()
{
var kurzversion = new AlternativeLessonPath { Name = "Kurzversion", Description = "Bei Zeitmangel." };
var alternativePaths = new FakeAlternativeLessonPaths([kurzversion]);
var lesson = new Lesson
{
Topic = "Brechung", Date = new DateOnly(2025, 9, 1), StartTime = new TimeOnly(12, 0),
Phases =
[
new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 5, AlternativePathId = kurzversion.Id },
new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 5 }, // Hauptweg, aber nach der Alternative angelegt
new LessonPhaseStep { Name = "Sicherung (kurz)", DurationMinutes = 5, AlternativePathId = kurzversion.Id },
new LessonPhaseStep { Name = "Erarbeitung", DurationMinutes = 20 },
],
};
var vm = new LessonViewerViewModel(lesson, alternativePaths);
Assert.True(vm.HasAlternatives);
Assert.Equal(2, vm.PhaseGroups.Count);
Assert.Equal("Hauptweg", vm.PhaseGroups[0].Label); // steht zuerst trotz Reihenfolge im Modell
Assert.True(vm.PhaseGroups[0].IsMainPath);
Assert.Null(vm.PhaseGroups[0].Description);
Assert.Equal(2, vm.PhaseGroups[0].Phases.Count);
Assert.Equal("Kurzversion", vm.PhaseGroups[1].Label);
Assert.False(vm.PhaseGroups[1].IsMainPath);
Assert.Equal("Bei Zeitmangel.", vm.PhaseGroups[1].Description);
Assert.Equal(2, vm.PhaseGroups[1].Phases.Count);
}
[Fact]
public void JedeGruppeBerechnetIhreEigeneUhrzeitAbStundenbeginn()
{
var planB = new AlternativeLessonPath { Name = "Plan B" };
var alternativePaths = new FakeAlternativeLessonPaths([planB]);
var lesson = new Lesson
{
Topic = "Brechung", Date = new DateOnly(2025, 9, 1), StartTime = new TimeOnly(12, 0),
Phases =
[
new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 5 },
new LessonPhaseStep { Name = "Vertiefung", DurationMinutes = 30, AlternativePathId = planB.Id },
],
};
var vm = new LessonViewerViewModel(lesson, alternativePaths);
// Beide Gruppen starten unabhängig voneinander bei Stundenbeginn, nicht hintereinander.
Assert.Equal("ab 12:00", vm.PhaseGroups[0].Phases[0].TimeDisplay);
Assert.Equal("ab 12:00", vm.PhaseGroups[1].Phases[0].TimeDisplay);
}
}