Files
LehrerApp/LehrerApp.Desktop.Tests/LessonDialogViewModelTests.cs
adminandClaude Sonnet 5 038337997f feat: Anhänge je Stunde, Klausur-Sitzplan mischen, Gefährdungsbeurteilungs-Assistent
Lesson bekommt dieselbe Anhang-Infrastruktur wie Documentation (Material,
Arbeitsblätter, Experimentunterlagen), samt Fix einer Sync-Lücke, die Anhang-
Dateibytes bisher nur für Documentation statt generisch übertragen hat
(IHasAttachments). Sitzplan-Tab bekommt einen "Plätze mischen"-Button für
Klausursitzpläne. Neu: mehrschrittiger Gefährdungsbeurteilungs-Assistent mit
optionalem KI-Entwurf (ai-backend/gbu.php) und PDF-Export, Format bewusst als
JSON-Anhang statt eigener Datenbank-Entität. Details und Architekturentscheidungen
in TODO.md (4.2, 7.1.5, 10.1.8).

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

585 lines
23 KiB
C#

using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class LessonDialogViewModelTests
{
private static PeriodScheduleService NewPeriodSchedule()
{
var tempPath = System.IO.Path.Combine(
System.IO.Path.GetTempPath(), $"lehrerapp-lessondialogvm-tests-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempPath);
return new PeriodScheduleService(tempPath);
}
private static LessonDialogViewModel BuildVm(Guid unitId, Guid groupId, Lesson? editing = null,
FakeTimetableSlots? slots = null, PeriodScheduleService? periodSchedule = null) =>
new(new FakeLessons(), new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(), new FakeAttachmentStorage(),
unitId, groupId, "10c", "Chemie", [], [], editing);
/// Nutzer-Feedback: bei einer Klasse, die in mehreren Fächern unterrichtet wird (mehrere
/// LearningGroup-Datensätze mit gleichem Namen), war im Stunden-Dialog nicht erkennbar,
/// welche der beiden Stunden man gerade bearbeitet.
[Fact]
public void GroupSubjectDisplay_ZeigtGruppeUndFachZusammen()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
Assert.Equal("10c · Chemie", vm.GroupSubjectDisplay);
}
[Fact]
public void AddPhase_FuegtZeileMitStandardwertenHinzu()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddPhaseCommand.Execute(null);
Assert.Single(vm.Phases);
Assert.Equal(5, vm.Phases[0].DurationMinutes);
Assert.False(vm.Phases[0].HasAlternativePath);
}
[Fact]
public void RemovePhase_EntferntZeileUndAktualisiertGesamtdauer()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 20;
vm.Phases[0].RemoveCommand.Execute(null);
Assert.Empty(vm.Phases);
Assert.Equal("0 Minuten gesamt", vm.TotalDurationDisplay);
}
[Fact]
public void MoveDown_VertauschtReihenfolgeDerPhasen()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].Name = "Einstieg";
vm.AddPhaseCommand.Execute(null);
vm.Phases[1].Name = "Erarbeitung";
vm.Phases[0].MoveDownCommand.Execute(null);
Assert.Equal("Erarbeitung", vm.Phases[0].Name);
Assert.Equal("Einstieg", vm.Phases[1].Name);
}
[Fact]
public void Shorthand_IstFreitextOhneErzwungeneVonNachStruktur()
{
// Kurzsymbol muss nicht immer ein Materialfluss-Pfeil sein — manchmal nur eine
// Sozialform ohne Pfeil (z.B. "Plenum", "LDE").
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddPhaseCommand.Execute(null);
var phase = vm.Phases[0];
phase.Shorthand = "Plenum";
Assert.Equal("Plenum", phase.Shorthand);
phase.Shorthand = "AB001->S";
Assert.Equal("AB001->S", phase.Shorthand);
}
[Fact]
public void ShorthandSuggestions_KombiniertKatalogUndBisherigeStundenwerte()
{
var codes = new FakeShorthandCodes([new ShorthandCode { Code = "Tb" }, new ShorthandCode { Code = "SH" }]);
var vm = new LessonDialogViewModel(new FakeLessons(), codes, new FakeAlternativeLessonPaths([]),
new FakeTimetableSlots(), NewPeriodSchedule(), new FakeAttachmentStorage(),
Guid.NewGuid(), Guid.NewGuid(), "10c", "Chemie", [], ["Plenum", "LDE", "Tb"], null); // "Tb" doppelt (Katalog + Historie), soll nur einmal erscheinen
Assert.Equal(["LDE", "Plenum", "SH", "Tb"], vm.ShorthandSuggestions);
}
[Fact]
public void RecomputeTimes_LeitetUhrzeitJePhaseAusBeginnUndKumulierterDauerAb()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.StartTimeText = "12:00";
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 15;
vm.AddPhaseCommand.Execute(null);
vm.Phases[1].DurationMinutes = 10;
Assert.Equal("ab 12:00", vm.Phases[0].ComputedTimeDisplay);
Assert.Equal("ab 12:15", vm.Phases[1].ComputedTimeDisplay);
Assert.Equal("25 Minuten gesamt", vm.TotalDurationDisplay);
}
[Fact]
public void RecomputeTimes_OhneBeginnBleibtDieUhrzeitanzeigeLeer()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 15;
Assert.Equal("", vm.Phases[0].ComputedTimeDisplay);
}
[Fact]
public void Save_ErfordertThemaUndGueltigesDatum()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.Topic = ""; vm.DateText = "keinDatum";
vm.SaveCommand.Execute(null);
Assert.Null(vm.Result);
Assert.NotEqual("", vm.TopicError);
Assert.NotEqual("", vm.DateTextError);
}
[Fact]
public void Save_UngueltigesBeginnFormatWirdAbgelehnt()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.Topic = "Thema"; vm.DateText = "01.09.2025"; vm.StartTimeText = "nicht-uhrzeit";
vm.SaveCommand.Execute(null);
Assert.Null(vm.Result);
Assert.NotEqual("", vm.StartTimeTextError);
}
[Fact]
public void Save_SetztUnitIdGroupIdUndUebernimmtPhasenUndBeginn()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var lessons = new FakeLessons();
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
new FakeTimetableSlots(), NewPeriodSchedule(), new FakeAttachmentStorage(), unitId, groupId, "10c", "Chemie", [], [], null)
{
Topic = "Brechung", DateText = "01.09.2025", StartTimeText = "11:45",
};
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].Name = "Einstieg";
vm.Phases[0].DurationMinutes = 10;
vm.Phases[0].Activity = "Begrüßung";
vm.Phases[0].Material = "AB01";
vm.Phases[0].Shorthand = "L->AB01";
vm.SaveCommand.Execute(null);
Assert.NotNull(vm.Result);
Assert.Equal(unitId, vm.Result!.UnitId);
Assert.Equal(groupId, vm.Result.GroupId);
Assert.Equal(new TimeOnly(11, 45), vm.Result.StartTime);
Assert.Single(vm.Result.Phases);
Assert.Equal("Einstieg", vm.Result.Phases[0].Name);
Assert.Equal(10, vm.Result.Phases[0].DurationMinutes);
Assert.Equal("L->AB01", vm.Result.Phases[0].Shorthand);
Assert.Single(lessons.GetByUnit(unitId));
}
[Fact]
public void AddAttachment_LaedtHochUndSaveUebernimmtIhnInDasErgebnis()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.Topic = "Thema"; vm.DateText = "01.09.2025";
vm.AddAttachment("gbu-natrium.pdf", new MemoryStream([1, 2, 3]));
vm.SaveCommand.Execute(null);
Assert.NotNull(vm.Result);
var attachment = Assert.Single(vm.Result!.Attachments);
Assert.Equal("gbu-natrium.pdf", attachment.FileName);
Assert.Equal(3, attachment.SizeBytes);
}
[Fact]
public void AddAttachment_ZuGrosseDateiWirdAbgelehnt()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddAttachment("riesig.pdf", new MemoryStream(new byte[IAttachmentStorage.MaxSizeBytes + 1]));
Assert.Empty(vm.Attachments);
Assert.NotEqual("", vm.AttachmentError);
}
[Fact]
public void RemoveAttachmentCommand_EntferntDenAnhangAusDerListe()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.AddAttachment("gbu-natrium.pdf", new MemoryStream([1, 2, 3]));
var item = vm.Attachments[0];
vm.RemoveAttachmentCommand.Execute(item);
Assert.Empty(vm.Attachments);
}
[Fact]
public void BearbeiteteStunde_LaedtVorhandeneAnhaengeVor()
{
var editing = new Lesson
{
Attachments = [new DocumentAttachment { StorageId = "abc", FileName = "gbu.pdf", SizeBytes = 42 }],
};
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), editing);
var attachment = Assert.Single(vm.Attachments);
Assert.Equal("gbu.pdf", attachment.FileName);
}
[Fact]
public void HasHomeworkText_OhneHausaufgabe_IstFalse()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
Assert.False(vm.HasHomeworkText);
vm.Homework = " ";
Assert.False(vm.HasHomeworkText);
vm.Homework = "S. 42";
Assert.True(vm.HasHomeworkText);
}
[Fact]
public void Save_UebernimmtHomeworkCheckedUndDismissed()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.Topic = "Brechung"; vm.DateText = "01.09.2025";
vm.Homework = "S. 42"; vm.HomeworkChecked = true;
vm.SaveCommand.Execute(null);
Assert.True(vm.Result!.HomeworkChecked);
Assert.False(vm.Result.HomeworkCheckDismissed);
}
[Fact]
public void HomeworkChecked_UndHomeworkCheckDismissed_SchliessenSichGegenseitigAus()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.HomeworkChecked = true;
Assert.False(vm.HomeworkCheckDismissed);
vm.HomeworkCheckDismissed = true;
Assert.False(vm.HomeworkChecked);
}
[Fact]
public void Bearbeiten_UebernimmtGespeicherteHomeworkFlags()
{
var editing = new Lesson
{
Topic = "Alt", Date = new DateOnly(2025, 9, 1), Homework = "S. 42",
HomeworkCheckDismissed = true,
};
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), editing);
Assert.True(vm.HomeworkCheckDismissed);
Assert.False(vm.HomeworkChecked);
}
[Fact]
public void HasAlternativePath_AngehaktOeffnetZuweisungUndUebernimmtErgebnis()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
var path = new AlternativeLessonPath { Name = "Kurzversion" };
vm.OnPickAlternativePath = _ => Task.FromResult<AlternativeLessonPath?>(path);
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].HasAlternativePath = true;
Assert.True(vm.Phases[0].HasAlternativePath);
Assert.Equal("Kurzversion", vm.Phases[0].AlternativePathName);
Assert.Equal(path.Id, vm.Phases[0].AlternativePathId);
}
[Fact]
public void HasAlternativePath_AbbruchDerZuweisungLaesstEsUnangehakt()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
vm.OnPickAlternativePath = _ => Task.FromResult<AlternativeLessonPath?>(null); // abgebrochen
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].HasAlternativePath = true;
Assert.False(vm.Phases[0].HasAlternativePath);
Assert.Null(vm.Phases[0].AlternativePathId);
}
[Fact]
public void HasAlternativePath_AbwaehlenEntferntZuordnungOhneDialogZuOeffnen()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
var path = new AlternativeLessonPath { Name = "Kurzversion" };
var dialogCalls = 0;
vm.OnPickAlternativePath = _ => { dialogCalls++; return Task.FromResult<AlternativeLessonPath?>(path); };
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].HasAlternativePath = true; // zuweisen -> 1 Dialogaufruf
vm.Phases[0].HasAlternativePath = false; // abwählen -> kein weiterer Dialogaufruf
Assert.Equal(1, dialogCalls);
Assert.False(vm.Phases[0].HasAlternativePath);
Assert.Equal("", vm.Phases[0].AlternativePathName);
Assert.Null(vm.Phases[0].AlternativePathId);
}
[Fact]
public void Save_UebernimmtZugewiesenenAlternativePfad()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid());
var path = new AlternativeLessonPath { Name = "Kurzversion" };
vm.OnPickAlternativePath = _ => Task.FromResult<AlternativeLessonPath?>(path);
vm.Topic = "Thema"; vm.DateText = "01.09.2025";
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].HasAlternativePath = true;
vm.SaveCommand.Execute(null);
Assert.Equal(path.Id, vm.Result!.Phases[0].AlternativePathId);
}
[Fact]
public void AddPhaseInternal_LoestBestehendeZuordnungBeimBearbeitenAuf()
{
var path = new AlternativeLessonPath { Name = "Kurzversion" };
var alternativePaths = new FakeAlternativeLessonPaths([path]);
var editing = new Lesson
{
Topic = "Thema", Date = new DateOnly(2025, 9, 1),
Phases = [new LessonPhaseStep { Name = "Einstieg", AlternativePathId = path.Id }],
};
var vm = new LessonDialogViewModel(new FakeLessons(), new FakeShorthandCodes([]), alternativePaths,
new FakeTimetableSlots(), NewPeriodSchedule(), new FakeAttachmentStorage(), Guid.NewGuid(), Guid.NewGuid(), "10c", "Chemie", [], [], editing);
Assert.True(vm.Phases[0].HasAlternativePath);
Assert.Equal("Kurzversion", vm.Phases[0].AlternativePathName);
}
// ── Zeitbedarf-Rückmeldung (4.2.2 Nachtrag: Stundenraster) ───────────────────
[Fact]
public void RecomputeTimeBudget_EinzelneStunde_ZeigtVerfuegbareZeitAusStundenraster()
{
var groupId = Guid.NewGuid();
var date = new DateOnly(2025, 9, 1);
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) }]);
var vm = BuildVm(Guid.NewGuid(), groupId, periodSchedule: periodSchedule);
vm.DateText = date.ToString("dd.MM.yyyy");
vm.LessonNumber = 3;
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 40;
Assert.True(vm.HasTimeBudgetInfo);
Assert.Contains("40 von 45 Minuten geplant", vm.TimeBudgetLabel);
}
[Fact]
public void RecomputeTimeBudget_Doppelstunde_AddiertBeidePerioden()
{
var groupId = Guid.NewGuid();
var date = new DateOnly(2025, 9, 1);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 3 });
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 4 });
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([
new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) },
new PeriodTimeEntry { PeriodNumber = 4, Start = new TimeOnly(10, 30), End = new TimeOnly(11, 15) },
]);
var vm = BuildVm(Guid.NewGuid(), groupId, slots: slots, periodSchedule: periodSchedule);
vm.DateText = date.ToString("dd.MM.yyyy");
vm.LessonNumber = 3;
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 84;
Assert.True(vm.HasTimeBudgetInfo);
Assert.Contains("84 von 90 Minuten geplant", vm.TimeBudgetLabel);
}
[Fact]
public void RecomputeTimeBudget_FolgeperiodeGehoertAndererGruppe_WirdNichtMitgezaehlt()
{
var groupId = Guid.NewGuid();
var otherGroupId = Guid.NewGuid();
var date = new DateOnly(2025, 9, 1);
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 3 });
slots.Add(new TimetableSlot { GroupId = otherGroupId, Weekday = date.DayOfWeek, PeriodNumber = 4 });
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([
new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) },
new PeriodTimeEntry { PeriodNumber = 4, Start = new TimeOnly(10, 30), End = new TimeOnly(11, 15) },
]);
var vm = BuildVm(Guid.NewGuid(), groupId, slots: slots, periodSchedule: periodSchedule);
vm.DateText = date.ToString("dd.MM.yyyy");
vm.LessonNumber = 3;
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 40;
Assert.Contains("40 von 45 Minuten geplant", vm.TimeBudgetLabel);
}
[Fact]
public void RecomputeTimeBudget_FarbeGruenImZielbereich()
{
var date = new DateOnly(2025, 9, 1);
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]);
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
vm.DateText = date.ToString("dd.MM.yyyy");
vm.LessonNumber = 1;
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 42; // 42/45 ≈ 93 % — Zielbereich
Assert.Equal("#43A047", vm.TimeBudgetColorHex);
}
[Fact]
public void RecomputeTimeBudget_FarbeDunkelrotBeiDeutlicherUeberplanung()
{
var date = new DateOnly(2025, 9, 1);
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]);
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
vm.DateText = date.ToString("dd.MM.yyyy");
vm.LessonNumber = 1;
vm.AddPhaseCommand.Execute(null);
vm.Phases[0].DurationMinutes = 60; // 60/45 ≈ 133 % — deutlich überplant
Assert.Equal("#B71C1C", vm.TimeBudgetColorHex);
}
[Fact]
public void LessonNumberGeaendert_UebernimmtBeginnAusStundenraster_WennNochKeinerEingetragen()
{
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 5, Start = new TimeOnly(11, 45), End = new TimeOnly(12, 30) }]);
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
vm.LessonNumber = 5;
Assert.Equal("11:45", vm.StartTimeText);
}
[Fact]
public void LessonNumberGeaendert_UeberschreibtBereitsEingetragenenBeginnNicht()
{
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 5, Start = new TimeOnly(11, 45), End = new TimeOnly(12, 30) }]);
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
vm.StartTimeText = "09:00";
vm.LessonNumber = 5;
Assert.Equal("09:00", vm.StartTimeText);
}
[Fact]
public void RecomputeTimeBudget_OhneStundenrasterEintrag_KeineRueckmeldung()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid()); // leeres PeriodScheduleService
vm.DateText = "01.09.2025";
vm.LessonNumber = 1;
vm.AddPhaseCommand.Execute(null);
Assert.False(vm.HasTimeBudgetInfo);
}
// ── Datumsvorschlag für eine neue Stunde (Nutzer-Feedback: Feld stand sonst immer auf
// "heute", was die Doppelstunden-Erkennung stillschweigend auf eine Einzelperiode
// zurückfallen ließ, wenn heute zufällig nicht der Unterrichtstag ist) ────────────────────
[Fact]
public void NeueStunde_OhneStundenplanEintraege_BleibtBeimHeutigenDatum()
{
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid()); // FakeTimetableSlots leer
Assert.Equal(DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy"), vm.DateText);
}
[Fact]
public void NeueStunde_LetzteStundeInDerZukunft_SchlaegtNaechstenPassendenWochentagDanachVor()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var futureLessonDate = new DateOnly(2099, 1, 6); // Dienstag
var lessons = new FakeLessons();
lessons.Add(new Lesson { UnitId = unitId, GroupId = groupId, Date = futureLessonDate, LessonNumber = 3 });
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 });
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
slots, NewPeriodSchedule(), new FakeAttachmentStorage(), unitId, groupId, "10c", "Chemie", [], [], null);
// Nächster Dienstag nach dem 06.01.2099 (einem Dienstag) ist der 13.01.2099.
Assert.Equal("13.01.2099", vm.DateText);
Assert.Equal(3, vm.LessonNumber);
}
[Fact]
public void NeueStunde_LetzteStundeInDerVergangenheit_SchlaegtNaechstenPassendenWochentagAbHeuteVor()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var lessons = new FakeLessons();
lessons.Add(new Lesson { UnitId = unitId, GroupId = groupId, Date = new DateOnly(2000, 1, 1), LessonNumber = 3 });
var weekday = DateTime.Today.DayOfWeek; // garantiert am selben oder folgenden Tag erreichbar
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = weekday, PeriodNumber = 3 });
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
slots, NewPeriodSchedule(), new FakeAttachmentStorage(), unitId, groupId, "10c", "Chemie", [], [], null);
var suggested = DateOnly.ParseExact(vm.DateText, "dd.MM.yyyy");
Assert.True(suggested >= DateOnly.FromDateTime(DateTime.Today));
Assert.Equal(weekday, suggested.DayOfWeek);
Assert.Equal(3, vm.LessonNumber);
}
[Fact]
public void NeueStunde_Doppelstunde_SchlaegtDieErstePeriodeAlsStundennummerVor()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var futureLessonDate = new DateOnly(2099, 1, 6); // Dienstag
var lessons = new FakeLessons();
lessons.Add(new Lesson { UnitId = unitId, GroupId = groupId, Date = futureLessonDate, LessonNumber = 3 });
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = DayOfWeek.Tuesday, PeriodNumber = 4 });
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 });
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
slots, NewPeriodSchedule(), new FakeAttachmentStorage(), unitId, groupId, "10c", "Chemie", [], [], null);
Assert.Equal(3, vm.LessonNumber); // frühere Periode, nicht 4
}
[Fact]
public void NeueStunde_MitStundenvorschlag_UebernimmtBeginnAusStundenraster()
{
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
var weekday = DateTime.Today.DayOfWeek;
var slots = new FakeTimetableSlots();
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = weekday, PeriodNumber = 2 });
var periodSchedule = NewPeriodSchedule();
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 2, Start = new TimeOnly(8, 50), End = new TimeOnly(9, 35) }]);
var vm = new LessonDialogViewModel(new FakeLessons(), new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
slots, periodSchedule, new FakeAttachmentStorage(), unitId, groupId, "10c", "Chemie", [], [], null);
Assert.Equal(2, vm.LessonNumber);
Assert.Equal("08:50", vm.StartTimeText);
}
}