Semantische Brushes (Light/Dark) lösen die fest codierten Dark-Hex-Werte ab, die im hellen Theme unbenutzbar waren. Trend-Chart zeigt jetzt einen echten gestapelten Balken über die letzten 7 Schultage statt drei unabhängig skalierten über Kalendertage; neue Fehlquote je Schüler*in seit Schuljahresbeginn ergänzt den Heute-Snapshot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
237 lines
9.4 KiB
C#
237 lines
9.4 KiB
C#
using LehrerApp.Core.Models;
|
|
using LehrerApp.Desktop.Services;
|
|
using LehrerApp.Desktop.ViewModels.ClassTeacher;
|
|
using Xunit;
|
|
|
|
namespace LehrerApp.Desktop.Tests;
|
|
|
|
public sealed class ClassTeacherViewModelsTests
|
|
{
|
|
[Fact]
|
|
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
|
{
|
|
var entries = new[]
|
|
{
|
|
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "HED", "Che", "Absent",
|
|
"Krank", 1, "24.08.26", true, null, 3, "nicht entsch.", false),
|
|
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "PAN", "Kunst", null,
|
|
"Krank", 2, "24.08.26", true, null, 5, "nicht entsch.", true),
|
|
new UntisClassAbsenceEntryDto("Andere Schuelerin", 2, "6a", 20260824, 1, 20, "BEN", "Deu",
|
|
"Verspätung", null, 3, "24.08.26", false, "Zug verpasst", 1, "entsch.", false),
|
|
};
|
|
|
|
var rows = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(entries);
|
|
|
|
Assert.Equal(2, rows.Count);
|
|
|
|
var max = rows[1];
|
|
Assert.Equal("Muster Max", max.StudentName);
|
|
Assert.Equal(2, max.TotalAbsentPeriods);
|
|
Assert.Equal(90, max.TotalAbsentMinutes);
|
|
Assert.Equal(["Che", "Kunst"], max.Subjects);
|
|
Assert.Equal([3, 5], max.PeriodNumbers.Order());
|
|
Assert.Equal(["nicht entsch."], max.Statuses);
|
|
Assert.Equal(["Absent"], max.AbsenceReasons);
|
|
Assert.Equal("Krank", max.Note);
|
|
Assert.Null(max.ExcuseNote);
|
|
Assert.True(max.CountsAsFullDay);
|
|
|
|
var andere = rows[0];
|
|
Assert.Equal("Andere Schuelerin", andere.StudentName);
|
|
Assert.Equal(1, andere.TotalAbsentPeriods);
|
|
Assert.Equal(20, andere.TotalAbsentMinutes);
|
|
Assert.Equal(["Deu"], andere.Subjects);
|
|
Assert.Null(andere.Note);
|
|
Assert.Equal("Zug verpasst", andere.ExcuseNote);
|
|
Assert.False(andere.CountsAsFullDay);
|
|
}
|
|
|
|
[Fact]
|
|
public void GroupByStudentAndDay_TrenntGleichenNamenAnUnterschiedlichenTagen()
|
|
{
|
|
var entries = new[]
|
|
{
|
|
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "HED", "Che", null,
|
|
null, 1, "24.08.26", true, null, 3, "nicht entsch.", false),
|
|
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260825, 1, 45, "HED", "Che", null,
|
|
null, 2, "25.08.26", true, null, 3, "nicht entsch.", false),
|
|
};
|
|
|
|
var rows = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(entries);
|
|
|
|
Assert.Equal(2, rows.Count);
|
|
Assert.Equal(new DateOnly(2026, 8, 25), rows[0].Date);
|
|
Assert.Equal(new DateOnly(2026, 8, 24), rows[1].Date);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_OrdnetFehlzeitPerExternKeyUndKlassenbuchPerNameZu()
|
|
{
|
|
var students = new[]
|
|
{
|
|
Student(1001, "Ada Müller"),
|
|
Student(1002, "Ben Schmidt"),
|
|
Student(1003, "Cem Yilmaz"),
|
|
};
|
|
var todayAbsences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Ada Müller", 1001, 2, 90,
|
|
["Che"], [3, 4], ["nicht entsch."], ["Absent"], null, null, false),
|
|
};
|
|
var recentClassRegister = new[]
|
|
{
|
|
new UntisForeignClassRegisterEventDto("6a", 20260824, "Deu", "Ben Schmidt", "mueller",
|
|
"Vergessen", "Organisation", "Buch vergessen"),
|
|
};
|
|
|
|
var roster = ClassTeacherRosterRow.Build(students, todayAbsences, recentClassRegister);
|
|
|
|
Assert.Equal(3, roster.Count);
|
|
var ada = Assert.Single(roster, r => r.StudentName == "Ada Müller");
|
|
Assert.True(ada.HasAbsenceToday);
|
|
Assert.Contains("nicht entsch.", ada.AbsenceTooltip);
|
|
Assert.False(ada.HasRecentClassRegisterEntry);
|
|
|
|
var ben = Assert.Single(roster, r => r.StudentName == "Ben Schmidt");
|
|
Assert.False(ben.HasAbsenceToday);
|
|
Assert.True(ben.HasRecentClassRegisterEntry);
|
|
|
|
var cem = Assert.Single(roster, r => r.StudentName == "Cem Yilmaz");
|
|
Assert.False(cem.HasAbsenceToday);
|
|
Assert.False(cem.HasRecentClassRegisterEntry);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_ErkenntNamenAuchInVertauschterReihenfolge()
|
|
{
|
|
// WebUntis liefert Namen im Schülerreport als "Vorname Nachname", in Fehlzeiten-/
|
|
// Klassenbuchberichten aber typischerweise als "Nachname Vorname" - der Namensabgleich
|
|
// muss das auch ohne ExternKey erkennen (Regressionstest für genau diesen Bug).
|
|
var students = new[] { Student(externKey: null, displayName: "Ben Schmidt") };
|
|
var todayAbsences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Schmidt Ben", null, 1, 45,
|
|
["Che"], [3], ["nicht entsch."], ["Absent"], null, null, false),
|
|
};
|
|
var recentClassRegister = new[]
|
|
{
|
|
new UntisForeignClassRegisterEventDto("6a", 20260824, "Deu", "Schmidt Ben", "mueller",
|
|
"Vergessen", "Organisation", "Buch vergessen"),
|
|
};
|
|
|
|
var roster = ClassTeacherRosterRow.Build(students, todayAbsences, recentClassRegister);
|
|
|
|
var ben = Assert.Single(roster);
|
|
Assert.True(ben.HasAbsenceToday);
|
|
Assert.True(ben.HasRecentClassRegisterEntry);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_BeschreibtVerspaetungNichtAlsNullFehlstunden()
|
|
{
|
|
var students = new[] { Student(1001, "Ada Müller") };
|
|
var todayAbsences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 26), "Müller Ada", 1001, 0, 20,
|
|
["Deu"], [1], ["nicht entsch."], ["Verspätung"], null, null, false),
|
|
};
|
|
|
|
var row = Assert.Single(ClassTeacherRosterRow.Build(students, todayAbsences, [],
|
|
new DateOnly(2026, 8, 26)));
|
|
|
|
Assert.True(row.IsLate);
|
|
Assert.True(row.IsUnexcused);
|
|
Assert.Equal("20 Min. verspätet · Unentschuldigt", row.StatusText);
|
|
Assert.Equal(ClassTeacherStatusKind.Danger, row.StatusKind);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_PriorisiertHandlungsbedarfVorUnauffaelligenSchuelern()
|
|
{
|
|
var students = new[]
|
|
{
|
|
Student(1001, "Zora Unauffällig"),
|
|
Student(1002, "Ada Fehlzeit"),
|
|
Student(1003, "Ben Klassenbuch"),
|
|
};
|
|
var absences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 26), "Fehlzeit Ada", 1002, 2, 90,
|
|
["Deu"], [1, 2], ["nicht entsch."], ["Absent"], null, null, false),
|
|
};
|
|
var register = new[]
|
|
{
|
|
new UntisForeignClassRegisterEventDto("10c", 20260825, "Deu", "Klassenbuch Ben", "test",
|
|
"Fehlende HA", "Negativ", "Hausaufgaben fehlen"),
|
|
};
|
|
|
|
var rows = ClassTeacherRosterRow.Build(students, absences, register, new DateOnly(2026, 8, 26));
|
|
|
|
Assert.Equal(["Ada Fehlzeit", "Ben Klassenbuch", "Zora Unauffällig"],
|
|
rows.Select(r => r.StudentName));
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_BerechnetFehlquoteSeitSchuljahresbeginn()
|
|
{
|
|
var students = new[] { Student(1001, "Ada Müller") };
|
|
var yearAbsences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 10), "Müller Ada", 1001, 2, 90,
|
|
["Che"], [1, 2], ["entsch."], ["Krank"], null, null, false),
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 17), "Müller Ada", 1001, 6, 270,
|
|
["Che"], [1, 2, 3, 4, 5, 6], ["nicht entsch."], ["Absent"], null, null, true),
|
|
};
|
|
|
|
var row = Assert.Single(ClassTeacherRosterRow.Build(students, [], [],
|
|
new DateOnly(2026, 8, 26), yearAbsences, schoolDaysElapsed: 20));
|
|
|
|
Assert.True(row.HasYearSummary);
|
|
Assert.Equal(2, row.YearAbsenceDayCount);
|
|
Assert.Equal(1, row.YearUnexcusedDayCount);
|
|
Assert.Equal(10, row.YearAbsenceRatePercent);
|
|
Assert.Contains("10 %", row.YearSummaryLabel);
|
|
Assert.Contains("2 von 20", row.YearSummaryTooltip);
|
|
Assert.Contains("1 unentschuldigt", row.YearSummaryTooltip);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_OhneJahresdatenZeigtKeineFehlquote()
|
|
{
|
|
var students = new[] { Student(1001, "Ada Müller") };
|
|
|
|
var row = Assert.Single(ClassTeacherRosterRow.Build(students, [], [], new DateOnly(2026, 8, 26)));
|
|
|
|
Assert.False(row.HasYearSummary);
|
|
Assert.Equal("", row.YearSummaryLabel);
|
|
Assert.Null(row.YearSummaryTooltip);
|
|
}
|
|
|
|
[Fact]
|
|
public void RosterBuild_OrdnetJahresfehlzeitenAuchOhneExternKeyUeberNamenZu()
|
|
{
|
|
var students = new[] { Student(externKey: null, displayName: "Ben Schmidt") };
|
|
var yearAbsences = new[]
|
|
{
|
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 10), "Schmidt Ben", null, 2, 90,
|
|
["Che"], [1, 2], ["entsch."], ["Krank"], null, null, false),
|
|
};
|
|
|
|
var row = Assert.Single(ClassTeacherRosterRow.Build(students, [], [], new DateOnly(2026, 8, 26),
|
|
yearAbsences, schoolDaysElapsed: 10));
|
|
|
|
Assert.Equal(1, row.YearAbsenceDayCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void TrendDay_BalkenbreitenSummierenSichZurGesamtbreite()
|
|
{
|
|
var day = new ClassTeacherTrendDay("Mo", 5, 2, 1, 2, 40d, 20d, 40d);
|
|
|
|
Assert.Equal(100d, day.TotalBarWidth);
|
|
}
|
|
|
|
private static UntisStudentRosterCacheEntry Student(int? externKey, string displayName) =>
|
|
new() { ClassName = "6a", ExternKey = externKey, DisplayName = displayName };
|
|
}
|