Files
LehrerApp/LehrerApp.Desktop.Tests/StudentPerformanceOverviewViewModelTests.cs
T
adminandClaude Sonnet 5 1215d4ac22
CI / build-and-test (push) Canceled after 0s
Tagesflagge im Sitzplan, Leistungsüberblick mit Zielnoten-Rechner
- Mitarbeit: Tagesflagge (👑 Spitzentag / 😴 Schlaftag /  Schlechter Tag)
  je Schüler und Sitzung. Primär im Sitzplatz-Dialog (⇧1/2/3, ⇧X) mit
  Badge auf der Sitzplatz-Kachel; Fallback in der Schnelleingabe für
  Lerngruppen ohne Sitzplan.
- Noten: neuer Dialog "Überblick" in der Notenübersicht zeigt Klausuren,
  Mitarbeit- und sonstige Noten eines Schülers samt berechneter
  Zeugnisnote. Zielnoten-Rechner (ReportGradeTargetCalculator) beantwortet
  "was brauche ich noch für Note X", ein Was-wäre-wenn-Rechner simuliert
  eine zusätzliche Klausurnote. Bewerter-/Schülermodus per Umschalter im
  selben Fenster — Bewertermodus zeigt zusätzlich Kursdurchschnitt je
  Klausur und erlaubt das Bearbeiten von Mitarbeit-/Sonstige-Noten.
  Klausurverlauf als Balken-Sparkline wie die bestehende Notenentwicklung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 11:43:47 +02:00

147 lines
6.6 KiB
C#

using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using Xunit;
namespace LehrerApp.Desktop.Tests;
/// Tests für den Schüler-Leistungsüberblick (Nutzer-Feedback): Klausuren/Mitarbeit/Sonstige
/// im Überblick, berechnete Zeugnisnote, Zielnoten- und Was-wäre-wenn-Rechner sowie der
/// Bewerter-/Schülermodus-Unterschied (Kursdurchschnitt, Bearbeitbarkeit).
public sealed class StudentPerformanceOverviewViewModelTests
{
private static readonly Guid GroupId = Guid.NewGuid();
private static readonly Guid StudentId = Guid.NewGuid();
private static readonly Guid OtherStudentId = Guid.NewGuid();
private static StudentPerformanceOverviewViewModel BuildViewModel(
FakeExams? exams = null, FakeResults? results = null, FakeGrades? grades = null,
FakeSchemes? schemes = null, GradingSystem system = GradingSystem.Grades1To6)
{
var scheme = new GradingScheme { ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 };
var schemesRepo = schemes ?? new FakeSchemes();
if (schemes is null) schemesRepo.SetForGroup(GroupId, scheme);
return new StudentPerformanceOverviewViewModel(
grades ?? new FakeGrades(), exams ?? new FakeExams([]), results ?? new FakeResults(),
new FakeMemberships([]), schemesRepo, new GradingService(),
GroupId, StudentId, GroupType.Class, system, "Beispiel, Anna", "Testkurs", "2025/26");
}
[Fact]
public void ExamRows_ZeigtEigeneNoteUndKursdurchschnitt()
{
var exam = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = "K1", Date = new DateOnly(2025, 9, 10) };
var exams = new FakeExams([exam]);
var results = new FakeResults();
results.Add(new ExamResult { ExamId = exam.Id, StudentId = StudentId, Grade = "2" });
results.Add(new ExamResult { ExamId = exam.Id, StudentId = OtherStudentId, Grade = "4" });
var vm = BuildViewModel(exams, results);
var row = Assert.Single(vm.ExamRows);
Assert.Equal("2", row.OwnGradeDisplay);
Assert.Equal("3.0", row.ClassAverageDisplay);
}
[Fact]
public void ExamHistory_ZeigtEigenenVerlaufUndMarkiertNotenabfall()
{
var exam1 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = "K1", Date = new DateOnly(2025, 9, 10) };
var exam2 = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = "K2", Date = new DateOnly(2025, 9, 24) };
var exams = new FakeExams([exam1, exam2]);
var results = new FakeResults();
results.Add(new ExamResult { ExamId = exam1.Id, StudentId = StudentId, Grade = "2" });
results.Add(new ExamResult { ExamId = exam2.Id, StudentId = StudentId, Grade = "5" }); // Abfall + mangelhaft
var vm = BuildViewModel(exams, results);
Assert.Equal(2, vm.ExamHistory.Points.Count);
Assert.False(vm.ExamHistory.Points[0].IsWarning);
Assert.True(vm.ExamHistory.Points[1].IsWarning);
Assert.Contains("Abfall", vm.ExamHistory.Points[1].WarningText);
Assert.Contains("Versetzungsgefährdung", vm.ExamHistory.Points[1].WarningText);
Assert.True(vm.ExamHistory.Points[1].BarHeight < vm.ExamHistory.Points[0].BarHeight);
}
[Fact]
public void AktuelleZeugnisnote_KombiniertAlleDreiBereiche()
{
var exam = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = "K1", Date = new DateOnly(2025, 9, 10) };
var exams = new FakeExams([exam]);
var results = new FakeResults();
results.Add(new ExamResult { ExamId = exam.Id, StudentId = StudentId, Grade = "2" });
var grades = new FakeGrades();
grades.Add(new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Participation,
Date = new DateOnly(2025, 9, 5), Value = "3" });
grades.Add(new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Other,
Date = new DateOnly(2025, 9, 5), Value = "1" });
var vm = BuildViewModel(exams, results, grades);
// (2*50 + 3*40 + 1*10) / 100 = 2.3 -> kaufmännisch 2
Assert.Equal("Note 2", vm.CurrentReportGradeDisplay);
}
[Fact]
public void Zielnote_BerechnetNoetigenKlausurschnitt()
{
var grades = new FakeGrades();
grades.Add(new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Participation,
Date = new DateOnly(2025, 9, 5), Value = "3" });
grades.Add(new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Other,
Date = new DateOnly(2025, 9, 5), Value = "2" });
var vm = BuildViewModel(grades: grades);
vm.TargetGradeText = "2";
vm.TargetBucketName = "Klausuren";
// Ziel 2,0 = (x*50 + 3*40 + 2*10)/100 => 200 = 50x+120+20 => x = 1,2
Assert.Contains("1.2", vm.TargetResultDisplay);
}
[Fact]
public void WasWaereWenn_ZeigtZeugnisnoteMitZusaetzlicherKlausurAn()
{
var exam = new Exam { Id = Guid.NewGuid(), GroupId = GroupId, Title = "K1", Date = new DateOnly(2025, 9, 10) };
var exams = new FakeExams([exam]);
var results = new FakeResults();
results.Add(new ExamResult { ExamId = exam.Id, StudentId = StudentId, Grade = "2" });
var grades = new FakeGrades();
grades.Add(new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Participation,
Date = new DateOnly(2025, 9, 5), Value = "2" });
var vm = BuildViewModel(exams, results, grades);
vm.WhatIfExamGradeText = "6";
// Klausuren neu: (2+6)/2=4 (50%), Mitarbeit 2 (40%, Sonstige fehlt) -> (4*50+2*40)/90 = 3,11 -> kaufm. 3
Assert.Contains("3", vm.WhatIfResultDisplay);
}
[Fact]
public void SaveCommand_AufMitarbeitszeile_SpeichertUndAktualisiertDurchschnitt()
{
var grades = new FakeGrades();
var grade = new Grade { StudentId = StudentId, GroupId = GroupId, Category = GradeCategory.Participation,
Date = new DateOnly(2025, 9, 5), Value = "3" };
grades.Add(grade);
var vm = BuildViewModel(grades: grades);
var row = Assert.Single(vm.ParticipationRows);
row.Value = "1";
row.SaveCommand.Execute(null);
Assert.Equal("1.0", vm.ParticipationAverageDisplay);
Assert.Equal("1", grades.GetByStudentAndGroup(StudentId, GroupId).Single().Value);
}
[Fact]
public void OhneWerteImZeitraum_ZeigtHinweisStattZeugnisnote()
{
var vm = BuildViewModel();
Assert.Contains("Noch nicht berechenbar", vm.CurrentReportGradeDisplay);
}
}