using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Desktop.ViewModels.Groups; using Xunit; namespace LehrerApp.Desktop.Tests; /// Tests für den Zeugnisnoten-Assistenten: eigenständige Halbjahre (H1 nur als Kontext in H2), /// Endjahresnote aus den beiden Halbjahresnoten, Speichern/Festschreiben über dieselben /// ReportGrade-Datensätze wie der Zeugnisnoten-Dialog sowie die beiden Graph-Modi. public sealed class ReportGradeAssistantViewModelTests { private static readonly Guid GroupId = Guid.NewGuid(); private static readonly Student Anna = new() { Id = Guid.NewGuid(), FirstName = "Anna", LastName = "Beispiel" }; private static readonly Student Ben = new() { Id = Guid.NewGuid(), FirstName = "Ben", LastName = "Muster" }; private sealed class Fixture { public List Exams { get; } = []; public FakeResults Results { get; } = new(); public FakeGrades Grades { get; } = new(); public FakeReportGrades ReportGrades { get; } = new(); public List Sessions { get; } = []; public FakeEntries Entries { get; } = new(); public List Sections { get; } = []; public FakeInterimGrades InterimGrades { get; } = new(); public ReportGradeAssistantViewModel Build(DateOnly today, int gradeLevel = 8, InterimRunOptions? interim = null) { var schemes = new FakeSchemes(); schemes.SetForGroup(GroupId, new GradingScheme { ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 }); return new ReportGradeAssistantViewModel( Grades, new FakeExams(Exams), Results, new FakeStudents([Anna, Ben]), new FakeMemberships([]), schemes, ReportGrades, new FakeSessions(Sessions), Entries, new FakeAspects(), new FakeSections(Sections), InterimGrades, new AttendanceBalanceService(), new GradingService(), GroupId, GroupType.Class, GradingSystem.Grades1To6, "Testkurs", "2025/26", gradeLevel, today, interim); } public InterimRunDialogViewModel BuildRunDialog(DateOnly today, Guid? preselected = null) { var schemes = new FakeSchemes(); schemes.SetForGroup(GroupId, new GradingScheme { ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 }); return new InterimRunDialogViewModel( Grades, new FakeExams(Exams), Results, new FakeStudents([Anna, Ben]), new FakeMemberships([]), schemes, ReportGrades, new FakeSessions(Sessions), Entries, new FakeAspects(), new FakeSections(Sections), InterimGrades, new GradingService(), GroupId, GroupType.Class, GradingSystem.Grades1To6, "Testkurs", "2025/26", preselected, today); } public void AddSession(DateOnly date, int rating) { var session = new ParticipationSession { Id = Guid.NewGuid(), GroupId = GroupId, Date = date }; Sessions.Add(session); Entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = Anna.Id, Ratings = [new AspectRating { Key = "quality", Value = rating }] }); } public Exam AddExam(string title, DateOnly date, string annaGrade) { var exam = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = title, Date = date }; Exams.Add(exam); Results.Add(new ExamResult { ExamId = exam.Id, StudentId = Anna.Id, Grade = annaGrade }); return exam; } public void AddParticipation(DateOnly date, string value, string? note = null) => Grades.Add(new Grade { StudentId = Anna.Id, GroupId = GroupId, Category = GradeCategory.Participation, Date = date, Value = value, Note = note }); } private static readonly DateOnly InH1 = new(2025, 11, 10); private static readonly DateOnly InH2 = new(2026, 4, 20); [Fact] public void Vorbelegung_WaehltDasLaufendeHalbjahr() { Assert.Equal(ParticipationPeriod.H1, new Fixture().Build(InH1).SelectedPeriod.Period); Assert.Equal(ParticipationPeriod.H2, new Fixture().Build(InH2).SelectedPeriod.Period); } [Fact] public void Halbjahr_BerechnetNoteUndZeigtTeilbereiche() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.AddParticipation(new DateOnly(2025, 10, 15), "3"); var vm = f.Build(InH1); // (2*50 + 3*40) / 90 = 2,44 -> 2 Assert.Equal("2", vm.CalculatedDisplay); Assert.Contains("2,44", vm.CalculationDetail); Assert.Equal(["2,00", "3,00", "–"], vm.Tiles.Select(t => t.Value)); Assert.Equal("2", vm.Students[0].GradeDisplay); Assert.Equal(AssistantGradeStatus.Open, vm.Students[0].Status); } [Fact] public void ZweitesHalbjahr_ErstesZaehltNichtWirdAberAlsKontextGezeigt() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "5"); f.AddExam("K2", new DateOnly(2026, 3, 1), "2"); f.AddParticipation(new DateOnly(2025, 12, 1), "5"); var vm = f.Build(InH2); Assert.Equal("2", vm.CalculatedDisplay); Assert.Equal(2, vm.Works.Count); Assert.True(vm.Works[0].IsContext); Assert.False(vm.Works[1].IsContext); Assert.True(Assert.Single(vm.ParticipationGrades).IsContext); vm.ShowPreviousHalf = false; Assert.Equal("K2", Assert.Single(vm.Works).Title); Assert.Empty(vm.ParticipationGrades); Assert.Equal("2", vm.CalculatedDisplay); } [Fact] public void Schuljahr_VerrechnetHalbjahresnotenUndBevorzugtGespeicherteEntscheidung() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.AddExam("K2", new DateOnly(2026, 3, 1), "4"); var vm = f.Build(InH2); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); // (2 + 4) / 2 = 3 Assert.Equal("3", vm.CalculatedDisplay); Assert.Equal(["2", "4"], vm.Tiles.Select(t => t.Value)); // Pädagogisch übersteuerte Halbjahresnote geht in die Verrechnung ein, nicht die berechnete. f.ReportGrades.Save(new ReportGrade { StudentId = Anna.Id, GroupId = GroupId, Period = "1. Halbjahr", CalculatedValue = "2", OverrideValue = "1", OverrideReason = "starke Entwicklung" }); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.H2); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); // (1 + 4) / 2 = 2,5 -> kaufmännisch 3, pädagogisch 2 Assert.Equal("3", vm.CalculatedDisplay); Assert.Contains("2,50", vm.CalculationDetail); vm.RoundingRule = RoundingRule.Pedagogical; Assert.Equal("2", vm.CalculatedDisplay); } [Fact] public void Klasse10_PruefungsnotenZaehlenWieWeitereHalbjahre() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.AddExam("K2", new DateOnly(2026, 3, 1), "2"); var vm = f.Build(InH2, gradeLevel: 10); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); Assert.True(vm.CanEnterExamGrades); Assert.Equal("2", vm.CalculatedDisplay); // (2 + 2 + 5) / 3 = 3 vm.ProjectExamGrade = "5"; Assert.Equal("3", vm.CalculatedDisplay); Assert.Equal(3, vm.Tiles.Count); // (2 + 2 + 5 + 5) / 4 = 3,5 -> 4 vm.OralExamGrade = "5"; Assert.Equal("4", vm.CalculatedDisplay); Assert.Contains("4 Teilnoten", vm.CalculationDetail); vm.SaveCommand.Execute(null); var record = f.ReportGrades.GetByStudentGroupPeriod(Anna.Id, GroupId, "Gesamtes Schuljahr"); Assert.Equal("5", record!.ProjectExamGrade); Assert.Equal("5", record.OralExamGrade); Assert.Equal("4", record.CalculatedValue); Assert.Equal("4", vm.Students[0].GradeDisplay); } private static ReportGradeAssistantViewModel Klasse10Schuljahr(Fixture f, string h1, string h2) { f.AddExam("K1", new DateOnly(2025, 10, 1), h1); f.AddExam("K2", new DateOnly(2026, 3, 1), h2); var vm = f.Build(InH2, gradeLevel: 10); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); return vm; } [Fact] public void Klasse10_SchriftlichePruefungGehtMitEinemDrittelEin() { var vm = Klasse10Schuljahr(new Fixture(), "2", "2"); // § 14 Abs. 3 ObSchVO: (2 * 2 + 5) / 3 = 3,0 vm.WrittenExamGrade = "5"; Assert.Equal("3", vm.CalculatedDisplay); } [Fact] public void Klasse10_ZusaetzlicheMuendlicheImSchriftlichenFach_EinsZuEinsOhneRundung() { var f = new Fixture(); var vm = Klasse10Schuljahr(f, "4", "4"); vm.WrittenExamGrade = "6"; // (4 + 4 + 6) / 3 = 4,67 -> 5 Assert.Equal("5", vm.CalculatedDisplay); // § 13 Abs. 2 PrüfV: (6 + 3) / 2 = 4,5 — bleibt ungerundet; (4 + 4 + 4,5) / 3 = 4,17 -> 4 vm.AdditionalOralExamGrade = "3"; Assert.Equal("4", vm.CalculatedDisplay); Assert.Contains("4,17", vm.CalculationDetail); var tile = vm.Tiles.Single(t => t.Label == "Prüfungsleistung"); Assert.Equal("4,5", tile.Value); Assert.Contains("zwischen 4 und 5", tile.Hint); vm.SaveCommand.Execute(null); var record = f.ReportGrades.GetByStudentGroupPeriod(Anna.Id, GroupId, "Gesamtes Schuljahr"); Assert.Equal("6", record!.WrittenExamGrade); Assert.Equal("3", record.AdditionalOralExamGrade); } [Fact] public void GenauZwischenZweiNoten_WeistAufPaedagogischeEntscheidungHin() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.AddExam("K2", new DateOnly(2026, 3, 1), "3"); var vm = f.Build(InH2); Assert.Empty(vm.BorderlineHint); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); // (2 + 3) / 2 = 2,5 Assert.Contains("zwischen 2 und 3", vm.BorderlineHint); vm.ToggleLockCommand.Execute(null); Assert.Empty(vm.BorderlineHint); } [Fact] public void Klasse10_NachpruefungOhneAbschlusspruefung_ZweiZuEins() { var vm = Klasse10Schuljahr(new Fixture(), "5", "5"); // § 13 Abs. 3 PrüfV: (2 * 5 + 3) / 3 = 4,33 -> 4 vm.AdditionalOralExamGrade = "3"; Assert.Equal("4", vm.CalculatedDisplay); } [Fact] public void Klasse10_NurEinHalbjahr_UnterrichtlicheLeistungZaehltTrotzdemDoppelt() { var f = new Fixture(); f.AddExam("K2", new DateOnly(2026, 3, 1), "2"); var vm = f.Build(InH2, gradeLevel: 10); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); // (2 * 2 + 5) / 3 = 3 — nicht (2 + 5) / 2 = 3,5 -> 4 vm.OralExamGrade = "5"; Assert.Equal("3", vm.CalculatedDisplay); } [Fact] public void Klasse10_WiderspruechlichePruefungen_WerdenAbgelehnt() { var f = new Fixture(); var vm = Klasse10Schuljahr(f, "3", "3"); vm.WrittenExamGrade = "3"; vm.OralExamGrade = "2"; vm.SaveCommand.Execute(null); Assert.NotEmpty(vm.OralExamGradeError); vm.WrittenExamGrade = ""; vm.AdditionalOralExamGrade = "4"; vm.SaveCommand.Execute(null); Assert.NotEmpty(vm.AdditionalOralExamGradeError); Assert.Equal(0, f.ReportGrades.SavedCount); } [Fact] public void Klasse10_UngueltigePruefungsnote_WirdNichtGespeichert() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); var vm = f.Build(InH2, gradeLevel: 10); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); vm.ProjectExamGrade = "gut"; vm.SaveCommand.Execute(null); Assert.NotEmpty(vm.ProjectExamGradeError); Assert.Equal(0, f.ReportGrades.SavedCount); } [Fact] public void PruefungsnotenNurInKlasse10UndNurFuerDasSchuljahr() { var klasse8 = new Fixture().Build(InH2); klasse8.SelectedPeriod = klasse8.PeriodOptions.Single(p => p.Period == ParticipationPeriod.FullYear); Assert.False(klasse8.CanEnterExamGrades); Assert.False(new Fixture().Build(InH2, gradeLevel: 10).CanEnterExamGrades); } [Fact] public void Speichern_UebersteuernOhneBegruendung_WirdAbgelehnt() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "3"); var vm = f.Build(InH1); vm.OverrideValue = "2"; vm.SaveCommand.Execute(null); Assert.NotEmpty(vm.OverrideReasonError); Assert.Equal(0, f.ReportGrades.SavedCount); vm.OverrideReason = "mündlich deutlich stärker"; vm.SaveCommand.Execute(null); var record = f.ReportGrades.GetByStudentGroupPeriod(Anna.Id, GroupId, "1. Halbjahr"); Assert.NotNull(record); Assert.Equal("3", record.CalculatedValue); Assert.Equal("2", record.OverrideValue); Assert.Equal("2", vm.Students[0].GradeDisplay); Assert.Equal(AssistantGradeStatus.Saved, vm.Students[0].Status); } [Fact] public void SpeichernUndWeiter_GehtZumNaechstenSchueler() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "3"); var vm = f.Build(InH1); vm.SaveAndNextCommand.Execute(null); Assert.Equal(Ben.Id, vm.SelectedStudent!.StudentId); Assert.Equal("2 / 2", vm.ProgressText); Assert.False(vm.NextStudentCommand.CanExecute(null)); } [Fact] public void Festschreiben_FriertBerechnetenStandEin() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "3"); var vm = f.Build(InH1); vm.ToggleLockCommand.Execute(null); Assert.True(vm.IsLocked); Assert.Equal(AssistantGradeStatus.Locked, vm.Students[0].Status); // Eine später eingetragene Arbeit ändert den festgeschriebenen Stand nicht mehr. f.AddExam("K2", new DateOnly(2025, 12, 1), "1"); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.H2); vm.SelectedPeriod = vm.PeriodOptions.Single(p => p.Period == ParticipationPeriod.H1); Assert.Equal("3", vm.CalculatedDisplay); vm.ToggleLockCommand.Execute(null); Assert.False(vm.IsLocked); Assert.Equal("2", vm.CalculatedDisplay); } [Fact] public void Mitarbeitsgraph_AbschnitteBlaetternUndVerlaufAmStueck() { var f = new Fixture(); f.Sections.Add(new ParticipationSection { GroupId = GroupId, Label = "Abschnitt 1", StartDate = new DateOnly(2025, 8, 20), EndDate = new DateOnly(2025, 9, 30) }); f.Sections.Add(new ParticipationSection { GroupId = GroupId, Label = "Abschnitt 2", StartDate = new DateOnly(2025, 10, 1), EndDate = new DateOnly(2025, 10, 31) }); f.AddParticipation(new DateOnly(2025, 9, 30), "2", "Abschnitt: Abschnitt 1"); foreach (var date in new[] { new DateOnly(2025, 9, 2), new DateOnly(2025, 10, 7), new DateOnly(2025, 11, 4) }) { var session = new ParticipationSession { Id = Guid.NewGuid(), GroupId = GroupId, Date = date }; f.Sessions.Add(session); f.Entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = Anna.Id, Ratings = [new AspectRating { Key = "quality", Value = 1 }] }); } var vm = f.Build(InH1); Assert.True(vm.IsSectionMode); Assert.Equal("1 / 3", vm.SectionPosition); Assert.Contains("Abschnittsnote 2", vm.SectionTitle); Assert.Single(Assert.Single(vm.ChartGroups).Points); Assert.False(vm.PreviousSectionCommand.CanExecute(null)); vm.NextSectionCommand.Execute(null); Assert.Contains("keine Abschnittsnote", vm.SectionTitle); vm.NextSectionCommand.Execute(null); Assert.Contains("vorläufig", vm.SectionTitle); Assert.True(vm.ChartGroups[0].IsOpen); Assert.False(vm.NextSectionCommand.CanExecute(null)); vm.IsSectionMode = false; Assert.Equal(3, vm.ChartGroups.Count); Assert.Equal(3, vm.ChartGroups.Sum(g => g.Points.Count)); } // ── Zwischenstand (2.7.5) ──────────────────────────────────────────────── private static InterimRunOptions Sprechtag(params Guid[] students) => new(new DateOnly(2025, 11, 12), "Sprechtag", students); [Fact] public void Zwischenstand_RechnetNurBisZumStichtagUndMitLaufenderMitarbeit() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.AddExam("K2", new DateOnly(2025, 12, 1), "6"); // nach dem Stichtag f.AddSession(new DateOnly(2025, 10, 20), -2); // laufender Abschnitt, schwache Mitarbeit var vm = f.Build(InH1, interim: Sprechtag(Anna.Id)); Assert.True(vm.IsInterimMode); Assert.Equal(Anna.Id, Assert.Single(vm.Students).StudentId); Assert.Equal("K1", Assert.Single(vm.Works).Title); Assert.Equal("2", vm.InterimWritten); Assert.NotNull(vm.InterimOral); Assert.Contains("laufender Abschnitt", vm.Tiles.Single(t => t.Label == "Mitarbeit").Hint); // Die schwache laufende Mitarbeit zieht den Gesamtstand unter die reine Klausurnote. Assert.True(int.Parse(vm.InterimOverall!) > 2); Assert.Contains("12.11.2025", vm.CalculationDetail); // Die Zeugnisnoten-Berechnung selbst bleibt ohne laufenden Abschnitt. var report = f.Build(InH1); Assert.Equal("–", report.Tiles.Single(t => t.Label == "Mitarbeit").Value); } [Fact] public void Zwischenstand_NotierenSpeichertGetrenntVonDerZeugnisnote() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "3"); var vm = f.Build(InH1, interim: Sprechtag(Anna.Id, Ben.Id)); vm.InterimOverall = ""; vm.NoteInterimCommand.Execute(null); Assert.NotEmpty(vm.InterimOverallError); Assert.Empty(f.InterimGrades.All); vm.InterimOverall = "3-"; vm.InterimNote = "Mitarbeit muss besser werden"; vm.NoteInterimAndNextCommand.Execute(null); var entry = Assert.Single(f.InterimGrades.All); Assert.Equal(Anna.Id, entry.StudentId); Assert.Equal(new DateOnly(2025, 11, 12), entry.Date); Assert.Equal("Sprechtag", entry.Occasion); Assert.Equal("3", entry.WrittenValue); Assert.Equal("3-", entry.OverallValue); Assert.Equal(0, f.ReportGrades.SavedCount); Assert.Equal(AssistantGradeStatus.Noted, vm.Students[0].Status); Assert.Equal("3-", vm.Students[0].GradeDisplay); Assert.Equal(Ben.Id, vm.SelectedStudent!.StudentId); // Erneutes Notieren im selben Durchgang überschreibt statt zu verdoppeln. vm.SelectedStudent = vm.Students[0]; Assert.True(vm.HasInterimEntry); Assert.Equal("3-", vm.InterimOverall); vm.InterimOverall = "4"; vm.NoteInterimCommand.Execute(null); Assert.Equal("4", Assert.Single(f.InterimGrades.All).OverallValue); vm.RemoveInterimCommand.Execute(null); Assert.Empty(f.InterimGrades.All); Assert.Equal(AssistantGradeStatus.Open, vm.Students[0].Status); } [Fact] public void Zwischenstaende_ErscheinenImZeugnisnotenModusAlsListeUndMarker() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "3"); f.InterimGrades.Save(new InterimGrade { StudentId = Anna.Id, GroupId = GroupId, Date = new DateOnly(2025, 11, 12), Occasion = "Sprechtag", WrittenValue = "3", OverallValue = "3" }); var vm = f.Build(new DateOnly(2025, 12, 10)); var row = Assert.Single(vm.InterimEntries); Assert.Equal("12.11.2025 · Sprechtag", row.Title); Assert.Equal("schriftlich 3", row.Detail); Assert.Contains(vm.ChartGroups.SelectMany(g => g.Points), p => p.ExamLabel == "Zwischenstand: 3"); Assert.True(vm.IsReportMode); } [Fact] public void Startdialog_VorauswahlGefaehrdeteUndBestaetigen() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "5"); f.Results.Add(new ExamResult { ExamId = f.Exams[0].Id, StudentId = Ben.Id, Grade = "2" }); var vm = f.BuildRunDialog(new DateOnly(2025, 11, 12)); Assert.All(vm.Students, s => Assert.True(s.IsSelected)); Assert.Equal(["5", "2"], vm.Students.Select(s => s.ProvisionalDisplay)); vm.SelectAtRiskCommand.Execute(null); Assert.Equal([Anna.Id], vm.Students.Where(s => s.IsSelected).Select(s => s.StudentId)); vm.OccasionName = "Blauer Brief"; Assert.True(vm.Confirm()); Assert.Equal(new DateOnly(2025, 11, 12), vm.Result!.Date); Assert.Equal("Blauer Brief", vm.Result.Occasion); Assert.Equal([Anna.Id], vm.Result.StudentIds); } [Fact] public void Startdialog_EinzelfallUndValidierung() { var f = new Fixture(); var vm = f.BuildRunDialog(new DateOnly(2025, 11, 12), preselected: Ben.Id); Assert.Equal([Ben.Id], vm.Students.Where(s => s.IsSelected).Select(s => s.StudentId)); vm.OccasionName = "Elternanfrage"; vm.OccasionDetail = "Frau Muster"; Assert.Equal("Elternanfrage: Frau Muster", vm.Occasion); vm.SelectNoneCommand.Execute(null); Assert.False(vm.Confirm()); Assert.NotEmpty(vm.SelectionError); vm.DateText = "12.11.2030"; Assert.False(vm.Confirm()); Assert.NotEmpty(vm.DateTextError); } [Fact] public void Startdialog_BegonnenenDurchgangFortsetzen() { var f = new Fixture(); f.InterimGrades.Save(new InterimGrade { StudentId = Anna.Id, GroupId = GroupId, Date = new DateOnly(2025, 11, 12), Occasion = "Sprechtag: 1. Elternsprechtag", OverallValue = "3" }); var vm = f.BuildRunDialog(new DateOnly(2025, 12, 1)); vm.SelectedExistingRun = Assert.Single(vm.ExistingRuns); Assert.Equal("12.11.2025", vm.DateText); Assert.Equal("Sprechtag: 1. Elternsprechtag", vm.Occasion); Assert.Equal("notiert: 3", vm.Students.Single(s => s.StudentId == Anna.Id).NotedDisplay); Assert.Equal("", vm.Students.Single(s => s.StudentId == Ben.Id).NotedDisplay); } [Fact] public void Notenuebersicht_ZeigtZwischenstaendeAlsEigeneSpalteOhneEinflussAufGesamt() { var f = new Fixture(); f.AddExam("K1", new DateOnly(2025, 10, 1), "2"); f.InterimGrades.Save(new InterimGrade { StudentId = Anna.Id, GroupId = GroupId, Date = new DateOnly(2025, 11, 12), Occasion = "Sprechtag", OverallValue = "5" }); var vm = new GradeOverviewTabViewModel(f.Grades, new FakeExams(f.Exams), f.Results, new FakeStudents([Anna, Ben]), new FakeMemberships([]), f.InterimGrades, new GradingService()); vm.Initialize(GroupId, GradingSystem.Grades1To6, GroupType.Class, "Testkurs", "2025/26"); var column = vm.Columns.Last(); Assert.True(column.IsInterim); Assert.Equal("Zwischenstand 12.11. Sprechtag", column.Header); var anna = vm.Rows.Single(r => r.StudentId == Anna.Id); Assert.Equal("5", anna.Cells.Last()); Assert.Equal("2.00", anna.TotalDisplay); Assert.Equal("", vm.Rows.Single(r => r.StudentId == Ben.Id).Cells.Last()); } }