Kurs-Übersicht gefüllt: nächste Termine, Mitarbeitshinweis, offene Kontrollen/Dokumentationen, auffällige Fehlzeiten
Der bislang leere "Übersicht"-Tab je Kurs zeigt jetzt einen kompakten "Was steht an"-Überblick aus bereits vorhandenen Daten mit Klick-Durchsprung in den jeweiligen Tab: nächste Stunde/Klausur, Hinweis auf die letzte Mitarbeitssitzung, offene Hausaufgaben-Kontrolle, offene Entschuldigungen, offene Dokumentations-Entwürfe und auffällige Fehlzeiten. Die Fehlzeiten-Karte ist bewusst kein 1:1-Abbild der Dashboard-Warnung: erst ab einer Mindeststichprobe von 8 erfassten Terminen im Schuljahr gemeldet, damit ein einzelner Fehltag zu Schuljahresbeginn nicht sofort als auffällig gilt. Das Hauptdashboard hat dasselbe Problem noch, wird dort separat nachgezogen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ public sealed class GroupDetailViewModelTests
|
||||
var grades = new FakeGrades();
|
||||
|
||||
var vm = new GroupDetailViewModel(groups, students, memberships, subjects, exams, grades, tasks,
|
||||
new GroupOverviewViewModel(new FakeLessons(), exams, new FakeSessions([]), new FakeEntries(), students,
|
||||
new FakeDocumentation(), new AttendanceBalanceService(), new SchoolYearService()),
|
||||
new ParticipationTabViewModel(new FakeSessions([]), new FakeEntries(), new FakeAspects(),
|
||||
students, memberships, groups, new FakeCompetencyDomains()),
|
||||
new GradeOverviewTabViewModel(grades, exams, new FakeResults(), students, memberships, new GradingService()),
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
/// Tests für die Kurs-Übersicht: sie liest bewusst keine eigenen Daten, sondern zieht nur
|
||||
/// zusammen, was Planung/Klausuren/Mitarbeit/Dokumentation ohnehin schon verwalten.
|
||||
public sealed class GroupOverviewViewModelTests
|
||||
{
|
||||
private static GroupOverviewViewModel NewVm(FakeLessons? lessons = null, FakeExams? exams = null,
|
||||
FakeSessions? sessions = null, FakeEntries? entries = null, FakeStudents? students = null,
|
||||
FakeDocumentation? documentation = null) =>
|
||||
new(lessons ?? new FakeLessons(), exams ?? new FakeExams([]), sessions ?? new FakeSessions([]),
|
||||
entries ?? new FakeEntries(), students ?? new FakeStudents([]), documentation ?? new FakeDocumentation(),
|
||||
new AttendanceBalanceService(), new SchoolYearService());
|
||||
|
||||
private static (GroupOverviewViewModel Vm, FakeLessons Lessons, FakeExams Exams,
|
||||
FakeSessions Sessions, FakeEntries Entries, FakeStudents Students, Guid GroupId) BuildScenario(
|
||||
List<Lesson>? lessons = null, List<Exam>? exams = null)
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var lessonsRepo = new FakeLessons();
|
||||
foreach (var l in lessons ?? []) lessonsRepo.Add(l);
|
||||
var examsRepo = new FakeExams(exams ?? []);
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
var students = new FakeStudents([]);
|
||||
|
||||
var vm = NewVm(lessonsRepo, examsRepo, sessions, entries, students);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
return (vm, lessonsRepo, examsRepo, sessions, entries, students, groupId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaechsteStunde_FindetDieNaechsteGeplanteStundeInnerhalbDesZeitfensters()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(10), Topic = "Später", Status = LessonStatus.Planned });
|
||||
lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(3), Topic = "Brechung", Status = LessonStatus.Planned });
|
||||
lessons.Add(new Lesson { GroupId = groupId, Date = today.AddDays(1), Topic = "Schon vorbei", Status = LessonStatus.Conducted });
|
||||
|
||||
var vm = NewVm(lessons: lessons);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.True(vm.HasNextLesson);
|
||||
Assert.Contains("Brechung", vm.NextLessonLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaechsteStunde_OhneGeplanteStundeIstNichtVorhanden()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
Assert.False(vm.HasNextLesson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaechsteKlausur_FindetFruehesteKuenftigeKlausur()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var exams = new FakeExams([
|
||||
new Exam { GroupId = groupId, Date = today.AddDays(20), Title = "Später" },
|
||||
new Exam { GroupId = groupId, Date = today.AddDays(5), Title = "Klassenarbeit 2" },
|
||||
new Exam { GroupId = groupId, Date = today.AddDays(-3), Title = "Vergangen" },
|
||||
]);
|
||||
var vm = NewVm(exams: exams);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.True(vm.HasNextExam);
|
||||
Assert.Contains("Klassenarbeit 2", vm.NextExamLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mitarbeit_OhneSitzungenZeigtHinweisOhneWarnung()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
Assert.Contains("Noch keine Mitarbeitssitzung", vm.ParticipationHintLabel);
|
||||
Assert.False(vm.ParticipationHintIsStale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mitarbeit_LetzteSitzungVorMehrAls14TagenGiltAlsVeraltet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var sessions = new FakeSessions([new ParticipationSession { GroupId = groupId, Date = today.AddDays(-20) }]);
|
||||
var vm = NewVm(sessions: sessions);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.True(vm.ParticipationHintIsStale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Mitarbeit_LetzteSitzungKuerzlichGiltNichtAlsVeraltet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var sessions = new FakeSessions([new ParticipationSession { GroupId = groupId, Date = today.AddDays(-2) }]);
|
||||
var vm = NewVm(sessions: sessions);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.False(vm.ParticipationHintIsStale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffeneHausaufgabenKontrolle_ZeigtUnkontrollierteHausaufgabeDerLetztenStunde()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(new Lesson
|
||||
{
|
||||
GroupId = groupId, Date = today.AddDays(-2), Homework = "S. 42 Nr. 3",
|
||||
HomeworkChecked = false, HomeworkCheckDismissed = false,
|
||||
});
|
||||
var vm = NewVm(lessons: lessons);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.True(vm.HasOpenHomeworkCheck);
|
||||
Assert.Contains("S. 42 Nr. 3", vm.OpenHomeworkCheckLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffeneHausaufgabenKontrolle_VerschwindetSobaldKontrolliert()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(new Lesson
|
||||
{
|
||||
GroupId = groupId, Date = today.AddDays(-2), Homework = "S. 42 Nr. 3",
|
||||
HomeworkChecked = true,
|
||||
});
|
||||
var vm = NewVm(lessons: lessons);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.False(vm.HasOpenHomeworkCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffeneEntschuldigungen_ListetOffenePendenteEntschuldigungenUndErlaubtAufloesen()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-2) };
|
||||
var sessions = new FakeSessions([session]);
|
||||
var entries = new FakeEntries();
|
||||
var entry = new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.ExcusePending };
|
||||
entries.Add(entry);
|
||||
var students = new FakeStudents([student]);
|
||||
|
||||
var vm = NewVm(sessions: sessions, entries: entries, students: students);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
var item = Assert.Single(vm.OpenExcuses);
|
||||
Assert.Equal(student.FullName, item.StudentName);
|
||||
|
||||
item.MarkExcusedCommand.Execute(null);
|
||||
|
||||
Assert.Empty(vm.OpenExcuses);
|
||||
Assert.Equal(AttendanceStatus.Excused, entries.GetBySessionAndStudent(session.Id, student.Id)!.Attendance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffeneDokumentationen_ZaehltNurEntwuerfeDieserGruppeOderOhneGruppe()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var otherGroupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var students = new FakeStudents([student]);
|
||||
var documentation = new FakeDocumentation();
|
||||
documentation.Add(new Documentation { StudentId = student.Id, GroupId = groupId, IsDraft = true, Title = "Beobachtung" });
|
||||
documentation.Add(new Documentation { StudentId = student.Id, GroupId = null, IsDraft = true, Title = "Ohne Gruppe" });
|
||||
documentation.Add(new Documentation { StudentId = student.Id, GroupId = otherGroupId, IsDraft = true, Title = "Anderer Kurs" });
|
||||
documentation.Add(new Documentation { StudentId = student.Id, GroupId = groupId, IsDraft = false, Title = "Fertig" });
|
||||
|
||||
var vm = NewVm(students: students, documentation: documentation);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.Equal(2, vm.DraftDocumentationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuffaelligeFehlzeiten_WirdErstAbMindeststichprobeGemeldet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var students = new FakeStudents([student]);
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
|
||||
// Nur 3 erfasste Termine, davon 3 unentschuldigt (100 %) — trotz hoher Quote noch keine
|
||||
// Meldung, weil die Stichprobe zu klein ist (Schuljahresbeginn-Problem).
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-i - 1) };
|
||||
sessions.Save(session);
|
||||
entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = AttendanceStatus.Unexcused });
|
||||
}
|
||||
|
||||
var vm = NewVm(sessions: sessions, entries: entries, students: students);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
Assert.False(vm.HasAttendanceNotices);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuffaelligeFehlzeiten_MeldetHoheQuoteAbMindeststichprobe()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var groupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
|
||||
var students = new FakeStudents([student]);
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
|
||||
// 10 erfasste Termine, 3 davon unentschuldigt (30 % > 20 % Schwelle) — jetzt genug
|
||||
// Stichprobe für eine Meldung.
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var session = new ParticipationSession { GroupId = groupId, Date = today.AddDays(-i - 1) };
|
||||
sessions.Save(session);
|
||||
var status = i < 3 ? AttendanceStatus.Unexcused : AttendanceStatus.Present;
|
||||
entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = student.Id, Attendance = status });
|
||||
}
|
||||
|
||||
var vm = NewVm(sessions: sessions, entries: entries, students: students);
|
||||
vm.Initialize(groupId, "Testkurs");
|
||||
|
||||
var item = Assert.Single(vm.AttendanceNotices);
|
||||
Assert.Equal(student.FullName, item.StudentName);
|
||||
Assert.Equal(30.0, item.AbsenceRatePercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavigationCommands_RufenDelegatMitPassendemTabIndexAuf()
|
||||
{
|
||||
var (vm, _, _, _, _, _, _) = BuildScenario();
|
||||
var indices = new List<int>();
|
||||
vm.OnNavigateToTab = idx => indices.Add(idx);
|
||||
|
||||
vm.NavigateToPlanningCommand.Execute(null);
|
||||
vm.NavigateToExamsCommand.Execute(null);
|
||||
vm.NavigateToParticipationCommand.Execute(null);
|
||||
vm.NavigateToDocumentationCommand.Execute(null);
|
||||
|
||||
Assert.Equal([6, 4, 3, 8], indices);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user