Tagesflagge im Sitzplan, Leistungsüberblick mit Zielnoten-Rechner
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
- 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>
This commit is contained in:
@@ -103,6 +103,47 @@ public sealed class QuickInputViewModelTests
|
||||
Assert.Null(rows[0].GetRating("quality"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetDayHighlight_SpeichertAufDerZeileUndAktualisiertDieAnzeige()
|
||||
{
|
||||
var aspects = new List<AspectColumnDef>
|
||||
{
|
||||
new(new ParticipationAspect { Key = "quality", Label = "Qualität" }),
|
||||
};
|
||||
var rows = new List<ParticipationStudentRow>
|
||||
{
|
||||
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
|
||||
};
|
||||
var vm = new QuickInputViewModel(rows, aspects);
|
||||
|
||||
vm.SetDayHighlight(DayHighlightKind.Standout);
|
||||
|
||||
Assert.Equal(DayHighlightKind.Standout, rows[0].DayHighlight);
|
||||
Assert.Equal("👑", vm.CurrentStudentDayHighlightSymbol);
|
||||
Assert.Equal("Spitzentag", vm.CurrentStudentDayHighlightLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZurueckZuVorherigemSchueler_ZeigtDessenTagesflaggeWiederAn()
|
||||
{
|
||||
var aspects = new List<AspectColumnDef>
|
||||
{
|
||||
new(new ParticipationAspect { Key = "quality", Label = "Qualität" }),
|
||||
};
|
||||
var rows = new List<ParticipationStudentRow>
|
||||
{
|
||||
new(Guid.NewGuid(), "Anna", new ParticipationEntry(), aspects, []),
|
||||
new(Guid.NewGuid(), "Ben", new ParticipationEntry(), aspects, []),
|
||||
};
|
||||
var vm = new QuickInputViewModel(rows, aspects);
|
||||
|
||||
vm.SetDayHighlight(DayHighlightKind.Sleepy); // Anna
|
||||
vm.NextStudent(); // zu Ben, keine Flagge
|
||||
vm.PreviousStudent(); // zurück zu Anna
|
||||
|
||||
Assert.Equal(DayHighlightKind.Sleepy, vm.CurrentStudentDayHighlight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VorherigerAspekt_SpringtRueckwaertsMitUmlauf()
|
||||
{
|
||||
|
||||
@@ -472,6 +472,7 @@ public sealed class SeatingPlanViewModelTests
|
||||
vm.SetRatingByNumber(5);
|
||||
vm.ApplyAttendanceShortcut(1, clear: false);
|
||||
vm.ApplyHomeworkShortcut(7, clear: false);
|
||||
vm.ApplyDayHighlightShortcut(1, clear: false);
|
||||
|
||||
var session = Assert.Single(sessions.GetByGroup(groupId));
|
||||
Assert.Equal(DateOnly.FromDateTime(DateTime.Today), session.Date);
|
||||
@@ -481,6 +482,27 @@ public sealed class SeatingPlanViewModelTests
|
||||
Assert.Equal(AttendanceStatus.Present, entry.Attendance);
|
||||
Assert.Equal(HomeworkStatus.MissingOpen, entry.Homework);
|
||||
Assert.True(entry.HomeworkMissing);
|
||||
Assert.Equal(DayHighlightKind.Standout, entry.DayHighlight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SitzplatzBewertung_TagesflaggeKannWiederGeloeschtWerden()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var studentId = Guid.NewGuid();
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
var vm = new SeatAssessmentViewModel(sessions, entries, new FakeAspects(),
|
||||
groupId, studentId, "Beispiel, Anna", canEdit: true);
|
||||
|
||||
vm.ApplyDayHighlightShortcut(3, clear: false); // aus Versehen "Schlechter Tag" gesetzt
|
||||
vm.ApplyDayHighlightShortcut(null, clear: true);
|
||||
|
||||
var session = Assert.Single(sessions.GetByGroup(groupId));
|
||||
var entry = entries.GetBySessionAndStudent(session.Id, studentId)!;
|
||||
Assert.Null(entry.DayHighlight);
|
||||
Assert.Equal("Keine Markierung", vm.DayHighlightLabel);
|
||||
Assert.All(vm.DayHighlightChoices, c => Assert.Equal(c.Kind is null, c.IsSelected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user