CI / build-and-test (push) Canceled after 0s
- Klausuren: neuer ExamWeekLoadService zählt eigene Klausuren je ISO-Woche über alle Kurse hinweg (Klassen-Kollisionen deckt bereits der externe Klausurplaner ab). Hinweis direkt im ExamDialog beim Datum, zusätzlich neue Dashboard-Karte "Klausurwochen" für die 60-Tage-Vorausschau. - Mitarbeit: Schnelleingabe-Dialog kann eine Bewertung jetzt per Entf auf "nicht bewertet" zurücksetzen (bisher nur im Sitzplatz-Dialog möglich). - Sitzplan: Strichliste "gemeldet"/"gemeldet und drangekommen" direkt auf der Sitzplatz-Kachel (ParticipationEntry.RaisedHandCount/CalledOnCount); schlägt im Sitzplatz-Dialog eine Quantitätsbewertung vor (ein Klick zum Übernehmen, nie automatisch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
738 lines
32 KiB
C#
738 lines
32 KiB
C#
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_SchlaegtQuantitaetAusStrichlisteVor()
|
||
{
|
||
var groupId = Guid.NewGuid();
|
||
var studentId = Guid.NewGuid();
|
||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||
var session = new ParticipationSession { GroupId = groupId, Date = today };
|
||
var sessions = new FakeSessions([session]);
|
||
var entries = new FakeEntries();
|
||
entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = studentId, RaisedHandCount = 3 });
|
||
|
||
var vm = new SeatAssessmentViewModel(sessions, entries, new FakeAspects(),
|
||
groupId, studentId, "Beispiel, Anna", canEdit: true);
|
||
|
||
var quantityRow = vm.AspectRows.Single(r => r.Key == "quantity");
|
||
Assert.True(quantityRow.HasSuggestion);
|
||
Assert.Equal(0, quantityRow.SuggestedValue); // 3 gemeldet -> "∼" laut ParticipationCountSuggestion
|
||
Assert.Contains("3× gemeldet", quantityRow.SuggestionLabel);
|
||
|
||
var qualityRow = vm.AspectRows.Single(r => r.Key == "quality");
|
||
Assert.False(qualityRow.HasSuggestion);
|
||
|
||
quantityRow.ApplySuggestionCommand.Execute(null);
|
||
|
||
Assert.Equal(0, quantityRow.Value);
|
||
var updatedEntry = entries.GetBySessionAndStudent(session.Id, studentId)!;
|
||
Assert.Equal(0, updatedEntry.Ratings.Single(r => r.Key == "quantity").Value);
|
||
}
|
||
|
||
[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 void TallyRaisedHand_ErhoehtNurGemeldetZaehler()
|
||
{
|
||
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 entries = new FakeEntries();
|
||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||
new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]),
|
||
sessions, entries, new FakeAspects());
|
||
vm.Initialize(groupId, isReadOnly: false);
|
||
var seat = vm.Seats.Single();
|
||
|
||
seat.TallyRaisedHandCommand.Execute(null);
|
||
seat.TallyRaisedHandCommand.Execute(null);
|
||
|
||
Assert.Equal(2, seat.RaisedHandCount);
|
||
Assert.Equal(0, seat.CalledOnCount);
|
||
var session = Assert.Single(sessions.GetByGroup(groupId));
|
||
var entry = entries.GetBySessionAndStudent(session.Id, student.Id)!;
|
||
Assert.Equal(2, entry.RaisedHandCount);
|
||
Assert.Equal(0, entry.CalledOnCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void TallyCalledOn_ErhoehtBeideZaehler()
|
||
{
|
||
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 entries = new FakeEntries();
|
||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||
new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]),
|
||
sessions, entries, new FakeAspects());
|
||
vm.Initialize(groupId, isReadOnly: false);
|
||
var seat = vm.Seats.Single();
|
||
|
||
seat.TallyRaisedHandCommand.Execute(null); // erste Meldung, nicht drangekommen
|
||
seat.TallyCalledOnCommand.Execute(null); // zweite Meldung, drangekommen
|
||
|
||
Assert.Equal(2, seat.RaisedHandCount);
|
||
Assert.Equal(1, seat.CalledOnCount);
|
||
}
|
||
|
||
[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);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task AssessStudent_SitzungBereitsUeberAndereInstanzVerknuepft_LegtKeineZweiteAn()
|
||
{
|
||
// Regressionstest: Der Unterrichtsmodus läuft auf einer eigenen, per DI frisch aufgelösten
|
||
// SeatingPlanTabViewModel-Instanz (TimetableView.ShowTeachingMode), getrennt von der
|
||
// Instanz im Sitzplan-Tab der Kursübersicht — beide teilen sich nur das Repository. Wurde
|
||
// (wie hier simuliert) die Sitzung bereits über den Unterrichtsmodus mit der Stunde
|
||
// verknüpft, bevor im echten Sitzplan-Tab bewertet wird, darf letzterer keine zweite,
|
||
// unverknüpfte "Sitzplan"-Sitzung für denselben Tag anlegen.
|
||
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 memberships = new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]);
|
||
|
||
// Instanz A: Sitzplan-Tab der Kursübersicht, bereits initialisiert (kein SelectedSession).
|
||
var tabVm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||
memberships, sessions, new FakeEntries(), new FakeAspects());
|
||
tabVm.Initialize(groupId, isReadOnly: false);
|
||
tabVm.OnAssessStudent = _ => Task.CompletedTask;
|
||
|
||
// Instanz B: eigene, frische Instanz wie im Unterrichtsmodus — verknüpft die Sitzung mit
|
||
// der Stunde, ohne dass Instanz A davon erfährt.
|
||
var lesson = new Lesson { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" };
|
||
var teachingModeVm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||
memberships, sessions, new FakeEntries(), new FakeAspects());
|
||
teachingModeVm.Initialize(groupId, isReadOnly: false);
|
||
teachingModeVm.SelectOrCreateSessionForLesson(lesson);
|
||
|
||
var linked = Assert.Single(sessions.GetByGroup(groupId));
|
||
Assert.Equal(lesson.Id, linked.LessonId);
|
||
|
||
await tabVm.AssessStudent(tabVm.Seats.Single());
|
||
|
||
var session = Assert.Single(sessions.GetByGroup(groupId));
|
||
Assert.Equal(linked.Id, session.Id);
|
||
Assert.Equal(lesson.Id, session.LessonId);
|
||
Assert.Equal(session.Id, tabVm.SelectedSession?.Id);
|
||
}
|
||
|
||
[Fact]
|
||
public void ShuffleSeats_ErzeugtNeuenPlanMitVertauschtenPlaetzenUndBehaeltDenUrsprungsplan()
|
||
{
|
||
var groupId = Guid.NewGuid();
|
||
var anna = new Student { FirstName = "Anna", LastName = "A" };
|
||
var ben = new Student { FirstName = "Ben", LastName = "B" };
|
||
var cara = new Student { FirstName = "Cara", LastName = "C" };
|
||
var students = new FakeStudents([anna, ben, cara]);
|
||
var memberships = new FakeMemberships([
|
||
new GroupMembership { GroupId = groupId, StudentId = anna.Id },
|
||
new GroupMembership { GroupId = groupId, StudentId = ben.Id },
|
||
new GroupMembership { GroupId = groupId, StudentId = cara.Id },
|
||
]);
|
||
var plan = new SeatingPlan
|
||
{
|
||
GroupId = groupId, Name = "Klausur", Room = "B204", Rows = 1, Columns = 3,
|
||
Assignments =
|
||
[
|
||
new SeatAssignment { Row = 0, Column = 0, StudentId = anna.Id },
|
||
new SeatAssignment { Row = 0, Column = 1, StudentId = ben.Id },
|
||
new SeatAssignment { Row = 0, Column = 2, StudentId = cara.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.ShuffleSeatsCommand.Execute(null);
|
||
|
||
var all = plans.GetByGroup(groupId);
|
||
Assert.Equal(2, all.Count);
|
||
var shuffled = Assert.Single(all, p => p.Id != plan.Id);
|
||
Assert.Equal("B204", shuffled.Room);
|
||
Assert.Equal(1, shuffled.Rows);
|
||
Assert.Equal(3, shuffled.Columns);
|
||
// Dieselben drei Schüler auf denselben drei Koordinaten, nur die Zuordnung untereinander
|
||
// wurde neu gewürfelt.
|
||
Assert.Equal(new[] { anna.Id, ben.Id, cara.Id }.OrderBy(id => id),
|
||
shuffled.Assignments.Select(a => a.StudentId).OrderBy(id => id));
|
||
Assert.Equal(new[] { (0, 0), (0, 1), (0, 2) },
|
||
shuffled.Assignments.Select(a => (a.Row, a.Column)).OrderBy(t => t.Item2));
|
||
// Ursprungsplan bleibt unverändert erhalten.
|
||
Assert.Equal(3, plans.GetById(plan.Id)!.Assignments.Count);
|
||
Assert.Equal(vm.SelectedPlan?.Id, shuffled.Id);
|
||
}
|
||
|
||
[Fact]
|
||
public void ShuffleSeatsCommand_OhneBearbeitungsmodus_NichtAusfuehrbar()
|
||
{
|
||
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: false);
|
||
|
||
Assert.False(vm.ShuffleSeatsCommand.CanExecute(null));
|
||
|
||
vm.IsEditMode = true;
|
||
|
||
Assert.True(vm.ShuffleSeatsCommand.CanExecute(null));
|
||
}
|
||
}
|