using LehrerApp.Core.Models; using LehrerApp.Desktop.ViewModels.Groups; using LehrerApp.Desktop.Views.Groups; using Xunit; namespace LehrerApp.Desktop.Tests; public sealed class SeatingPlanViewModelTests { [Fact] public void TafelpositionWirdAusPlanGeladenUndBeimBearbeitenGespeichert() { var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, IsBoardAtBottom = true, }; var plans = new FakeSeatingPlans([plan]); var tabVm = new SeatingPlanTabViewModel( plans, new FakeStudents([]), new FakeMemberships([]), new FakeSessions([]), new FakeEntries(), new FakeAspects()); tabVm.Initialize(groupId, isReadOnly: false); Assert.True(tabVm.IsBoardAtBottom); Assert.False(tabVm.IsBoardAtTop); var dialogVm = tabVm.CreateDialogViewModel(plan); dialogVm.IsBoardAtBottom = false; dialogVm.SaveCommand.Execute(null); Assert.False(plans.GetById(plan.Id)!.IsBoardAtBottom); } [Fact] public void AuswahlEinesBereitsZugeordnetenSchuelers_VerschiebtIhnAufDenNeuenPlatz() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "Beispiel" }; var students = new FakeStudents([student]); var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = student.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }], }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanTabViewModel(plans, students, memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.Seats[1].SelectedOption = vm.StudentOptions.Single(o => o.StudentId == student.Id); Assert.Null(vm.Seats[0].SelectedOption.StudentId); var assignment = Assert.Single(plans.GetById(plan.Id)!.Assignments); Assert.Equal((0, 1, student.Id), (assignment.Row, assignment.Column, assignment.StudentId)); } [Fact] public void ArchivierteGruppe_DeaktiviertSitzplatzbearbeitung() { var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 }; var vm = new SeatingPlanTabViewModel( new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: true); Assert.False(vm.IsEditable); Assert.False(vm.Seats.Single().CanEdit); Assert.False(vm.AddPlanCommand.CanExecute(null)); } [Fact] public void DragDrop_ZwischenBelegtenPlaetzen_TauschtDieSchueler() { var groupId = Guid.NewGuid(); var anna = new Student { FirstName = "Anna", LastName = "A" }; var ben = new Student { FirstName = "Ben", LastName = "B" }; var students = new FakeStudents([anna, ben]); var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = anna.Id }, new GroupMembership { GroupId = groupId, StudentId = ben.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, Assignments = [ new SeatAssignment { Row = 0, Column = 0, StudentId = anna.Id }, new SeatAssignment { Row = 0, Column = 1, StudentId = ben.Id }, ], }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanTabViewModel(plans, students, memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.MoveSeat(vm.Seats[0], vm.Seats[1]); Assert.Equal(ben.Id, vm.Seats[0].SelectedOption.StudentId); Assert.Equal(anna.Id, vm.Seats[1].SelectedOption.StudentId); Assert.Equal(2, plans.GetById(plan.Id)!.Assignments.Count); } // ── Ausgeblendete Plätze (fehlender Tisch in einer Reihe) ───────────────────────────── [Fact] public void LoadPlan_LaedtAusgeblendetePlaetzeAusDemPlan() { var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, HiddenSeats = [new HiddenSeat { Row = 0, Column = 1 }], }; var vm = new SeatingPlanTabViewModel( new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); Assert.False(vm.Seats[0].IsHidden); Assert.True(vm.Seats[1].IsHidden); } [Fact] public void ToggleSeatHidden_LeererPlatz_BlendetAusUndSpeichertUndKannWiederEingeblendetWerden() { var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2 }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanTabViewModel(plans, new FakeStudents([]), new FakeMemberships([]), new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.ToggleSeatHidden(vm.Seats[1]); Assert.True(vm.Seats[1].IsHidden); var hidden = Assert.Single(plans.GetById(plan.Id)!.HiddenSeats); Assert.Equal((0, 1), (hidden.Row, hidden.Column)); vm.ToggleSeatHidden(vm.Seats[1]); Assert.False(vm.Seats[1].IsHidden); Assert.Empty(plans.GetById(plan.Id)!.HiddenSeats); } [Fact] public void ToggleSeatHidden_BelegterPlatz_TutNichts() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = student.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }], }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.ToggleSeatHidden(vm.Seats[0]); Assert.False(vm.Seats[0].IsHidden); } /// Ein ausgeblendeter Platz darf nie ein Ziel für Drag&Drop/Zuordnung sein - sonst hätte man /// gleichzeitig "kein Tisch hier" und einen dort sitzenden Schüler. [Fact] public void AssignStudent_AufAusgeblendetenPlatz_WirdIgnoriert() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = student.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, HiddenSeats = [new HiddenSeat { Row = 0, Column = 0 }], }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanTabViewModel(plans, new FakeStudents([student]), memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.AssignStudent(vm.StudentOptions.Single(o => o.StudentId == student.Id), vm.Seats[0]); Assert.False(vm.Seats[0].IsOccupied); Assert.Empty(plans.GetById(plan.Id)!.Assignments); } [Fact] public void MoveSeat_AufAusgeblendetenPlatz_WirdIgnoriert() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = student.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, HiddenSeats = [new HiddenSeat { Row = 0, Column = 1 }], Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }], }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanTabViewModel(plans, new FakeStudents([student]), memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.IsEditMode = true; vm.MoveSeat(vm.Seats[0], vm.Seats[1]); Assert.Equal(student.Id, vm.Seats[0].SelectedOption.StudentId); Assert.False(vm.Seats[1].IsOccupied); } // ── Unterrichtsmodus (14.x): Sitzung mit einer konkreten Stunde verknüpfen ──────────────── [Fact] public void SelectOrCreateSessionForLesson_BereitsVerknuepfteSitzungVorhanden_WaehltSieAus() { var groupId = Guid.NewGuid(); var lesson = new Lesson { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" }; var existingSession = new ParticipationSession { GroupId = groupId, Date = lesson.Date, LessonId = lesson.Id, Comment = "Redox" }; var sessions = new FakeSessions([existingSession]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), sessions, new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.SelectOrCreateSessionForLesson(lesson); Assert.Equal(existingSession.Id, vm.SelectedSession?.Id); Assert.Single(sessions.GetByGroup(groupId)); } [Fact] public void SelectOrCreateSessionForLesson_KeineSitzungVorhanden_LegtVerknuepfteAnUndWaehltSieAus() { var groupId = Guid.NewGuid(); var lesson = new Lesson { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" }; var sessions = new FakeSessions([]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), sessions, new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.SelectOrCreateSessionForLesson(lesson); var created = Assert.Single(sessions.GetByGroup(groupId)); Assert.Equal(lesson.Id, created.LessonId); Assert.Equal(lesson.Topic, created.Comment); Assert.Equal(vm.SelectedSession?.Id, created.Id); } [Fact] public void SelectOrCreateSessionForLesson_ArchivierteGruppe_LegtKeineSitzungAn() { var groupId = Guid.NewGuid(); var lesson = new Lesson { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" }; var sessions = new FakeSessions([]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), sessions, new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: true); vm.SelectOrCreateSessionForLesson(lesson); Assert.Empty(sessions.GetByGroup(groupId)); Assert.Null(vm.SelectedSession); } [Fact] public void ShowSeatUndCanToggleHidden_HaengenVonBearbeitungsmodusUndBelegungAb() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var memberships = new FakeMemberships([ new GroupMembership { GroupId = groupId, StudentId = student.Id }, ]); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, HiddenSeats = [new HiddenSeat { Row = 0, Column = 1 }], Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }], }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), memberships, new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); // Ansichtsmodus: der ausgeblendete Platz wird nicht gerendert, belegte Plätze können // ohnehin nicht ausgeblendet werden. Assert.False(vm.Seats[1].ShowSeat); Assert.False(vm.Seats[0].CanToggleHidden); Assert.False(vm.Seats[1].CanToggleHidden); vm.IsEditMode = true; // Bearbeitungsmodus: der ausgeblendete Platz bleibt sichtbar (abgeblendet), um ihn wieder // einblenden zu können - der belegte Platz kann weiterhin nicht ausgeblendet werden. Assert.True(vm.Seats[1].ShowSeat); Assert.False(vm.Seats[0].CanToggleHidden); Assert.True(vm.Seats[1].CanToggleHidden); } [Fact] public void Ansichtsmodus_VerhindertVerschiebenUndBlendetBearbeitungsbefehleAus() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 2, Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }], }; var vm = new SeatingPlanTabViewModel( new FakeSeatingPlans([plan]), new FakeStudents([student]), new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]), new FakeSessions([]), new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.MoveSeat(vm.Seats[0], vm.Seats[1]); Assert.False(vm.IsEditMode); Assert.False(vm.CanEditLayout); Assert.False(vm.EditPlanCommand.CanExecute(null)); Assert.Equal(student.Id, vm.Seats[0].SelectedOption.StudentId); Assert.Null(vm.Seats[1].SelectedOption.StudentId); vm.IsEditMode = true; Assert.True(vm.CanEditLayout); Assert.True(vm.EditPlanCommand.CanExecute(null)); Assert.All(vm.Seats, seat => Assert.True(seat.CanEdit)); } [Fact] public void Ansichtsmodus_ZeigtAnwesenheitUndHausaufgabeUndBlendetAbwesendeAb() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var session = new ParticipationSession { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today) }; var entries = new FakeEntries(); entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.Excused, Homework = HomeworkStatus.MissingOpen, }); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, Assignments = [new SeatAssignment { StudentId = student.Id }], }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]), new FakeSessions([session]), entries, new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); var seat = Assert.Single(vm.Seats); Assert.True(seat.HasAttendanceBadge); Assert.True(seat.HasHomeworkBadge); Assert.Equal(0.42, seat.LessonOpacity); } [Fact] public void SituationsTags_WerdenProSchuelerUndSitzungInEinemEntwurfZusammengefuehrt() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var session = new ParticipationSession { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), LessonId = Guid.NewGuid(), }; var docs = new FakeDocumentation(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, Assignments = [new SeatAssignment { StudentId = student.Id }], }; var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]), new FakeSessions([session]), new FakeEntries(), new FakeAspects(), docs); vm.Initialize(groupId, isReadOnly: false); var seat = Assert.Single(vm.Seats); seat.SituationTags.Single(t => t.Text == "Mitarbeit verweigert").ToggleCommand.Execute(null); seat.SituationTags.Single(t => t.Text == "Material vergessen").ToggleCommand.Execute(null); var draft = Assert.Single(docs.GetByStudent(student.Id)); Assert.True(draft.IsDraft); Assert.Equal(session.Id, draft.ParticipationSessionId); Assert.Equal(session.LessonId, draft.LessonId); Assert.Equal(["Mitarbeit verweigert", "Material vergessen"], draft.Tags); } [Fact] public void Tischabstaende_WerdenBeimBearbeitenProSpaltengrenzeGespeichert() { var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 2, Columns = 4, ColumnGapWidths = [0, 80, 20], }; var plans = new FakeSeatingPlans([plan]); var vm = new SeatingPlanDialogViewModel(plans, groupId, plan); Assert.Equal([0m, 80m, 20m], vm.ColumnGaps.Select(g => g.Width)); vm.ColumnGaps[0].Width = 40; vm.SaveCommand.Execute(null); Assert.Equal([40d, 80d, 20d], plans.GetById(plan.Id)!.ColumnGapWidths); } [Fact] public void SitzplanPanel_FuegtAbstandNurAnKonfigurierterSpaltengrenzeEin() { var panel = new SeatingPlanPanel { Columns = 3, ColumnGapWidths = [0, 70], }; for (var i = 0; i < 6; i++) panel.Children.Add(new Avalonia.Controls.Border { Width = 100, Height = 50 }); panel.Measure(Avalonia.Size.Infinity); panel.Arrange(new Avalonia.Rect(panel.DesiredSize)); Assert.Equal(370, panel.DesiredSize.Width); Assert.Equal(100, panel.DesiredSize.Height); Assert.Equal(0, panel.Children[0].Bounds.X); Assert.Equal(100, panel.Children[1].Bounds.X); Assert.Equal(270, panel.Children[2].Bounds.X); Assert.Equal(270, panel.Children[5].Bounds.X); } [Fact] public void SitzplatzBewertung_ErstelltHeutigeSitzungUndSpeichertAlleDreiBereiche() { var groupId = Guid.NewGuid(); var studentId = Guid.NewGuid(); var sessions = new FakeSessions([]); var entries = new FakeEntries(); var vm = new SeatAssessmentViewModel(sessions, entries, new FakeAspects(), groupId, studentId, "Beispiel, Anna", canEdit: true); vm.SetRatingByNumber(5); vm.ApplyAttendanceShortcut(1, clear: false); vm.ApplyHomeworkShortcut(7, clear: false); var session = Assert.Single(sessions.GetByGroup(groupId)); Assert.Equal(DateOnly.FromDateTime(DateTime.Today), session.Date); Assert.Equal("Sitzplan", session.Comment); var entry = entries.GetBySessionAndStudent(session.Id, studentId)!; Assert.Equal(2, entry.Ratings.Single(r => r.Key == "quality").Value); Assert.Equal(AttendanceStatus.Present, entry.Attendance); Assert.Equal(HomeworkStatus.MissingOpen, entry.Homework); Assert.True(entry.HomeworkMissing); } [Fact] public void SitzplatzBewertung_ArchiviertOhneSitzung_LegtKeineNeueSitzungAn() { var groupId = Guid.NewGuid(); var sessions = new FakeSessions([]); var vm = new SeatAssessmentViewModel(sessions, new FakeEntries(), new FakeAspects(), groupId, Guid.NewGuid(), "Beispiel, Anna", canEdit: false); vm.SetRatingByNumber(5); Assert.False(vm.CanEdit); Assert.Empty(sessions.GetByGroup(groupId)); } [Fact] public void Initialize_LegtOhneVorhandeneSitzungKeineGeistersitzungAn() { // Regressionstest: Initialize() läuft schon beim bloßen Navigieren zur Kursübersicht // (GroupDetailViewModel.LoadGroup ruft SeatingPlanTab.Initialize für jede Gruppe auf), // unabhängig davon, ob der Sitzplan-Tab überhaupt angesehen wird. Vorher legte // LoadTodaySessions() dabei sofort eine "Sitzplan"-Sitzung für heute an. var groupId = Guid.NewGuid(); var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 }; var sessions = new FakeSessions([]); var vm = new SeatingPlanTabViewModel( new FakeSeatingPlans([plan]), new FakeStudents([]), new FakeMemberships([]), sessions, new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); Assert.Empty(sessions.GetByGroup(groupId)); Assert.Null(vm.SelectedSession); } [Fact] public async Task AssessStudent_LegtErstBeiTatsaechlicherBewertungEineSitzungAn() { var groupId = Guid.NewGuid(); var student = new Student { FirstName = "Anna", LastName = "A" }; var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, Assignments = [new SeatAssignment { StudentId = student.Id }], }; var sessions = new FakeSessions([]); var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]), sessions, new FakeEntries(), new FakeAspects()); vm.Initialize(groupId, isReadOnly: false); vm.OnAssessStudent = _ => Task.CompletedTask; Assert.Empty(sessions.GetByGroup(groupId)); await vm.AssessStudent(vm.Seats.Single()); var session = Assert.Single(sessions.GetByGroup(groupId)); Assert.Equal(DateOnly.FromDateTime(DateTime.Today), session.Date); Assert.Equal("Sitzplan", session.Comment); Assert.Equal(session.Id, vm.SelectedSession?.Id); } }