- Schnellbewerten-Dialog: abwesende Schüler werden gedimmt und mit ihrem Anwesenheitsstatus statt der Aspektbeschriftung angezeigt, damit keine Mitarbeitsnote für nicht anwesende Schüler vergeben wird. - Zeugnisnoten-Dialog: zeigt je Schüler die Fehlquote im gewählten Zeitraum, ab 50 % hervorgehoben (informativ, keine automatische Notenänderung). - Gruppen-Tab "Dokumentation" (bisher Platzhalter) implementiert: listet alle Dokumentationseinträge der Gruppen-Schüler, mit Schüler-Filter und optionalem "Nur dieser Unterricht"-Schalter. Einträge aus anderen Lerngruppen werden standardmäßig mitangezeigt, aber gedimmt. Der Dokumentationsdialog bekommt dafür einen optionalen Schüler-Picker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
222 lines
10 KiB
C#
222 lines
10 KiB
C#
using LehrerApp.Core.Models;
|
||
using LehrerApp.Core.Services;
|
||
using LehrerApp.Desktop.ViewModels.Groups;
|
||
using Xunit;
|
||
|
||
namespace LehrerApp.Desktop.Tests;
|
||
|
||
/// Tests für die Zeugnisnotenberechnung (2.4): ReportGradeDialogViewModel kombiniert
|
||
/// Klausuren, Mitarbeit und sonstige Noten gemäß Gewichtungsschema zu einer Zeugnisnote.
|
||
public class ReportGradeCalculationTests
|
||
{
|
||
private static Guid GroupId { get; } = Guid.NewGuid();
|
||
private static Student Anna { get; } = new() { Id = Guid.NewGuid(), FirstName = "Anna", LastName = "Beispiel" };
|
||
private static Student Ben { get; } = new() { Id = Guid.NewGuid(), FirstName = "Ben", LastName = "Muster" };
|
||
|
||
private static ReportGradeDialogViewModel BuildViewModel(
|
||
FakeGrades grades, FakeSchemes schemes, FakeReportGrades reportGrades,
|
||
FakeExams exams, FakeResults results, FakeSessions? sessions = null, FakeEntries? entries = null)
|
||
{
|
||
var students = new FakeStudents([Anna, Ben]);
|
||
var memberships = new FakeMemberships([]);
|
||
var grading = new GradingService();
|
||
|
||
return new ReportGradeDialogViewModel(grades, exams, results, students, memberships,
|
||
schemes, reportGrades, sessions ?? new FakeSessions([]), entries ?? new FakeEntries(),
|
||
new AttendanceBalanceService(), grading, GroupId, GroupType.Class, GradingSystem.Grades1To6,
|
||
"Testgruppe", "2025/26");
|
||
}
|
||
|
||
private static (Exam Exam1, Exam Exam2, FakeExams Exams, FakeResults Results) BuildExams()
|
||
{
|
||
var exam1 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Date = new DateOnly(2025, 9, 10), Title = "K1" };
|
||
var exam2 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Date = new DateOnly(2025, 9, 24), Title = "K2" };
|
||
var exams = new FakeExams([exam1, exam2]);
|
||
var results = new FakeResults();
|
||
results.Add(new ExamResult { ExamId = exam1.Id, StudentId = Anna.Id, Grade = "2" });
|
||
results.Add(new ExamResult { ExamId = exam2.Id, StudentId = Anna.Id, Grade = "4" });
|
||
return (exam1, exam2, exams, results);
|
||
}
|
||
|
||
[Fact]
|
||
public void CalculatedGrade_KombiniertKlausurenMitarbeitUndSonstigeGemaessSchema()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var grades = new FakeGrades();
|
||
grades.Add(new Grade { StudentId = Anna.Id, GroupId = GroupId, Category = GradeCategory.Participation,
|
||
Date = new DateOnly(2025, 9, 5), Value = "3", Weight = 1.0 });
|
||
grades.Add(new Grade { StudentId = Anna.Id, GroupId = GroupId, Category = GradeCategory.Other,
|
||
Date = new DateOnly(2025, 9, 5), Value = "1", Weight = 1.0 });
|
||
var schemes = new FakeSchemes();
|
||
schemes.SetForGroup(GroupId, new GradingScheme { ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 });
|
||
|
||
var vm = BuildViewModel(grades, schemes, new FakeReportGrades(), exams, results);
|
||
|
||
// Klausuren Ø3 (Note 2 und 4), Mitarbeit 3, Sonstige 1 -> (3*50+3*40+1*10)/100 = 2.8 -> kaufmännisch 3
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
Assert.Equal("3", anna.CalculatedValue);
|
||
}
|
||
|
||
[Fact]
|
||
public void CalculatedGrade_SchuelerOhneJedeNote_ZeigtKeinenWert()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var schemes = new FakeSchemes();
|
||
var vm = BuildViewModel(new FakeGrades(), schemes, new FakeReportGrades(), exams, results);
|
||
|
||
var ben = vm.Rows.Single(r => r.StudentId == Ben.Id);
|
||
Assert.Null(ben.CalculatedValue);
|
||
Assert.Equal("–", ben.CalculatedDisplay);
|
||
}
|
||
|
||
[Fact]
|
||
public void AbsenceRatePercent_BerechnetFehlquoteAusSitzungenDieserGruppeImZeitraum()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var session1 = new ParticipationSession { GroupId = GroupId, Date = new DateOnly(2025, 9, 5) };
|
||
var session2 = new ParticipationSession { GroupId = GroupId, Date = new DateOnly(2025, 9, 12) };
|
||
var sessions = new FakeSessions([session1, session2]);
|
||
var entries = new FakeEntries();
|
||
entries.Add(new ParticipationEntry { SessionId = session1.Id, StudentId = Anna.Id, Attendance = AttendanceStatus.Present });
|
||
entries.Add(new ParticipationEntry { SessionId = session2.Id, StudentId = Anna.Id, Attendance = AttendanceStatus.Unexcused });
|
||
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), new FakeReportGrades(), exams, results,
|
||
sessions, entries);
|
||
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
Assert.Equal(50.0, anna.AbsenceRatePercent);
|
||
Assert.True(anna.HasHighAbsenceRate);
|
||
var ben = vm.Rows.Single(r => r.StudentId == Ben.Id);
|
||
Assert.Equal(0.0, ben.AbsenceRatePercent);
|
||
Assert.False(ben.HasHighAbsenceRate);
|
||
}
|
||
|
||
[Fact]
|
||
public void ResolveScheme_BevorzugtGruppenspezifischesSchemaVorVoreinstellung()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var grades = new FakeGrades();
|
||
var schemes = new FakeSchemes();
|
||
schemes.SetForGroup(GroupId, new GradingScheme { ExamsPercent = 100, ParticipationPercent = 0, OtherPercent = 0 });
|
||
schemes.SetDefault(GroupType.Class, new GradingScheme { ExamsPercent = 0, ParticipationPercent = 0, OtherPercent = 100 });
|
||
|
||
var vm = BuildViewModel(grades, schemes, new FakeReportGrades(), exams, results);
|
||
|
||
// Gruppenspezifisches Schema gewichtet nur Klausuren (Ø3 aus Note 2 und 4) -> exakt "3"
|
||
Assert.Contains("Klausuren 100", vm.SchemeSummary);
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
Assert.Equal("3", anna.CalculatedValue);
|
||
}
|
||
|
||
[Fact]
|
||
public void ResolveScheme_OhneGruppenschema_VerwendetVoreinstellungDesGruppentyps()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var schemes = new FakeSchemes();
|
||
schemes.SetDefault(GroupType.Class, new GradingScheme { ExamsPercent = 70, ParticipationPercent = 20, OtherPercent = 10 });
|
||
|
||
var vm = BuildViewModel(new FakeGrades(), schemes, new FakeReportGrades(), exams, results);
|
||
|
||
Assert.Contains("Klausuren 70", vm.SchemeSummary);
|
||
}
|
||
|
||
[Fact]
|
||
public void ResolveScheme_OhneJedeVoreinstellung_VerwendetHartcodiertenFallback()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), new FakeReportGrades(), exams, results);
|
||
|
||
Assert.Contains("Klausuren 50", vm.SchemeSummary);
|
||
Assert.Contains("Mitarbeit 40", vm.SchemeSummary);
|
||
Assert.Contains("Sonstige 10", vm.SchemeSummary);
|
||
}
|
||
|
||
[Fact]
|
||
public void Save_UebersteuernOhneBegruendung_LiefertValidierungsfehlerUndSpeichertNicht()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var reportGrades = new FakeReportGrades();
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), reportGrades, exams, results);
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
|
||
anna.OverrideValue = "1";
|
||
anna.SaveCommand.Execute(null);
|
||
|
||
Assert.False(string.IsNullOrEmpty(anna.ValidationMessage));
|
||
Assert.Equal(0, reportGrades.SavedCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void Save_UebersteuernMitBegruendung_AktualisiertFinalDisplay()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var reportGrades = new FakeReportGrades();
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), reportGrades, exams, results);
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
|
||
anna.OverrideValue = "1";
|
||
anna.OverrideReason = "Mündliche Steigerung im letzten Quartal";
|
||
anna.SaveCommand.Execute(null);
|
||
|
||
Assert.True(string.IsNullOrEmpty(anna.ValidationMessage));
|
||
Assert.Equal("1", anna.FinalDisplay);
|
||
Assert.Equal(1, reportGrades.SavedCount);
|
||
}
|
||
|
||
[Fact]
|
||
public void ToggleLock_FriertDenStandEinUndIgnoriertSpaetereNeuberechnung()
|
||
{
|
||
var (exam1, _, exams, results) = BuildExams();
|
||
var reportGrades = new FakeReportGrades();
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), reportGrades, exams, results);
|
||
var anna = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
anna.OverrideValue = "1";
|
||
anna.OverrideReason = "Testgrund";
|
||
anna.SaveCommand.Execute(null);
|
||
|
||
anna.ToggleLockCommand.Execute(null);
|
||
|
||
// Neue, bessere Klausurnote nachtragen -> würde die Berechnung ändern, wenn ungesperrt.
|
||
results.Add(new ExamResult { ExamId = exam1.Id, StudentId = Anna.Id, Grade = "1" });
|
||
vm.SelectedPeriod = vm.PeriodOptions.First(p => p.Period == ParticipationPeriod.H1);
|
||
vm.SelectedPeriod = vm.PeriodOptions.First(p => p.Period == ParticipationPeriod.FullYear);
|
||
|
||
var annaNachSperre = vm.Rows.Single(r => r.StudentId == Anna.Id);
|
||
Assert.True(annaNachSperre.IsLocked);
|
||
Assert.Equal("1", annaNachSperre.FinalDisplay);
|
||
}
|
||
|
||
[Fact]
|
||
public void ExportCsv_EnthaeltAlleSchuelerDerGruppe()
|
||
{
|
||
var (_, _, exams, results) = BuildExams();
|
||
var vm = BuildViewModel(new FakeGrades(), new FakeSchemes(), new FakeReportGrades(), exams, results);
|
||
|
||
var csv = vm.ExportCsv();
|
||
|
||
Assert.Contains("Beispiel, Anna", csv);
|
||
Assert.Contains("Muster, Ben", csv);
|
||
}
|
||
|
||
[Fact]
|
||
public void RoundingRule_Paedagogisch_RundetGrenzfallZurBesserenNote()
|
||
{
|
||
var exam1 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Date = new DateOnly(2025, 9, 10), Title = "K1" };
|
||
var exam2 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Date = new DateOnly(2025, 9, 24), Title = "K2" };
|
||
var exams = new FakeExams([exam1, exam2]);
|
||
var results = new FakeResults();
|
||
// Ø exakt 2.5 -> kaufmännisch 3, pädagogisch 2 (bessere Note)
|
||
results.Add(new ExamResult { ExamId = exam1.Id, StudentId = Anna.Id, Grade = "2" });
|
||
results.Add(new ExamResult { ExamId = exam2.Id, StudentId = Anna.Id, Grade = "3" });
|
||
var schemes = new FakeSchemes();
|
||
schemes.SetForGroup(GroupId, new GradingScheme { ExamsPercent = 100, ParticipationPercent = 0, OtherPercent = 0 });
|
||
var vm = BuildViewModel(new FakeGrades(), schemes, new FakeReportGrades(), exams, results);
|
||
|
||
Assert.Equal("3", vm.Rows.Single(r => r.StudentId == Anna.Id).CalculatedValue);
|
||
|
||
vm.RoundingRuleName = "Pädagogisch";
|
||
|
||
Assert.Equal("2", vm.Rows.Single(r => r.StudentId == Anna.Id).CalculatedValue);
|
||
}
|
||
}
|