Persönliche Klausurlast pro Woche, Mitarbeit-Reset und Sitzplan-Strichliste
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
- Klausuren: neuer ExamWeekLoadService zählt eigene Klausuren je ISO-Woche über alle Kurse hinweg (Klassen-Kollisionen deckt bereits der externe Klausurplaner ab). Hinweis direkt im ExamDialog beim Datum, zusätzlich neue Dashboard-Karte "Klausurwochen" für die 60-Tage-Vorausschau. - Mitarbeit: Schnelleingabe-Dialog kann eine Bewertung jetzt per Entf auf "nicht bewertet" zurücksetzen (bisher nur im Sitzplatz-Dialog möglich). - Sitzplan: Strichliste "gemeldet"/"gemeldet und drangekommen" direkt auf der Sitzplatz-Kachel (ParticipationEntry.RaisedHandCount/CalledOnCount); schlägt im Sitzplatz-Dialog eine Quantitätsbewertung vor (ein Klick zum Übernehmen, nie automatisch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels;
|
||||
using System.Globalization;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
@@ -75,6 +76,67 @@ public sealed class DashboardViewModelTests
|
||||
substitutions ?? new FakeSubstitutionEntries(), annualPlanEvents);
|
||||
}
|
||||
|
||||
/// Montag einer Woche, die garantiert in der Zukunft liegt und innerhalb der
|
||||
/// Klausurwochen-Vorausschau — unabhängig davon, welcher Wochentag "heute" gerade ist.
|
||||
private static DateOnly NextIsoWeekMonday()
|
||||
{
|
||||
var reference = DateTime.Today.AddDays(7);
|
||||
return DateOnly.FromDateTime(
|
||||
ISOWeek.ToDateTime(ISOWeek.GetYear(reference), ISOWeek.GetWeekOfYear(reference), DayOfWeek.Monday));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExamWeekLoads_DreiGeplanteKlausurenInDerselbenWoche_ErzeugtEintrag()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var monday = NextIsoWeekMonday();
|
||||
var exams = new FakeExams([
|
||||
new Exam { GroupId = group.Id, Title = "a", Date = monday, Status = ExamStatus.Planned },
|
||||
new Exam { GroupId = group.Id, Title = "b", Date = monday.AddDays(1), Status = ExamStatus.Planned },
|
||||
new Exam { GroupId = group.Id, Title = "c", Date = monday.AddDays(2), Status = ExamStatus.Planned },
|
||||
]);
|
||||
|
||||
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: exams);
|
||||
|
||||
var entry = Assert.Single(vm.ExamWeekLoads);
|
||||
Assert.Equal(3, entry.ExamCount);
|
||||
Assert.True(vm.ExamLoadCard.EffectiveIsVisible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExamWeekLoads_WenigeKlausurenProWoche_BleibtLeer()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var monday = NextIsoWeekMonday();
|
||||
var exams = new FakeExams([
|
||||
new Exam { GroupId = group.Id, Title = "a", Date = monday, Status = ExamStatus.Planned },
|
||||
new Exam { GroupId = group.Id, Title = "b", Date = monday.AddDays(1), Status = ExamStatus.Planned },
|
||||
]);
|
||||
|
||||
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: exams);
|
||||
|
||||
Assert.Empty(vm.ExamWeekLoads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExamWeekLoads_BereitsDurchgefuehrteKlausurenZaehlenNicht()
|
||||
{
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var monday = NextIsoWeekMonday();
|
||||
var exams = new FakeExams([
|
||||
new Exam { GroupId = group.Id, Title = "a", Date = monday, Status = ExamStatus.Conducted },
|
||||
new Exam { GroupId = group.Id, Title = "b", Date = monday.AddDays(1), Status = ExamStatus.Graded },
|
||||
new Exam { GroupId = group.Id, Title = "c", Date = monday.AddDays(2), Status = ExamStatus.Planned },
|
||||
]);
|
||||
|
||||
var vm = BuildVm(group, new Lesson { GroupId = group.Id, Date = today }, exams: exams);
|
||||
|
||||
Assert.Empty(vm.ExamWeekLoads);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TodaysLessons_LoestRaumUeberPassendenStundenplanSlotAuf()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class ExamDialogViewModelTests
|
||||
{
|
||||
private static ExamDialogViewModel BuildViewModel(List<Exam> existingExams, string dateText,
|
||||
Exam? editingExam = null)
|
||||
{
|
||||
var vm = new ExamDialogViewModel(new FakeExams(existingExams), new FakeCompetencyDomains(),
|
||||
new FakeGradingKeyTemplates(), new GradingService(), Guid.NewGuid(), null, 9,
|
||||
GradingSystem.Grades1To6, "Chemie", isDifferentiated: false, editingExam, null);
|
||||
vm.DateText = dateText;
|
||||
return vm;
|
||||
}
|
||||
|
||||
private static Exam MakeExam(DateOnly date) => new() { GroupId = Guid.NewGuid(), Title = "x", Date = date };
|
||||
|
||||
[Fact]
|
||||
public void WeekLoadHint_WenigeKlausurenInDerWoche_BleibtLeer()
|
||||
{
|
||||
var monday = new DateOnly(2026, 3, 16);
|
||||
var existing = new List<Exam> { MakeExam(monday) };
|
||||
|
||||
var vm = BuildViewModel(existing, monday.AddDays(1).ToString("dd.MM.yyyy"));
|
||||
|
||||
Assert.Equal("", vm.WeekLoadHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeekLoadHint_DritteKlausurInDerWoche_ZeigtHinweis()
|
||||
{
|
||||
var monday = new DateOnly(2026, 3, 16);
|
||||
var existing = new List<Exam> { MakeExam(monday), MakeExam(monday.AddDays(1)) };
|
||||
|
||||
var vm = BuildViewModel(existing, monday.AddDays(2).ToString("dd.MM.yyyy"));
|
||||
|
||||
Assert.Contains("3 Klausuren", vm.WeekLoadHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeekLoadHint_BearbeiteteKlausurZaehltNichtDoppelt()
|
||||
{
|
||||
var monday = new DateOnly(2026, 3, 16);
|
||||
var editing = MakeExam(monday);
|
||||
var existing = new List<Exam> { editing, MakeExam(monday.AddDays(1)) };
|
||||
|
||||
// Nur die eigene Klausur (editing) + eine weitere -> 2 insgesamt, kein Hinweis.
|
||||
var vm = BuildViewModel(existing, monday.ToString("dd.MM.yyyy"), editing);
|
||||
|
||||
Assert.Equal("", vm.WeekLoadHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeekLoadHint_UngueltigesDatum_BleibtLeer()
|
||||
{
|
||||
var vm = BuildViewModel([], "keindatum");
|
||||
|
||||
Assert.Equal("", vm.WeekLoadHint);
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,26 @@ public sealed class QuickInputViewModelTests
|
||||
Assert.Equal(0.4, vm.CurrentStudentContentOpacity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearRating_SetztVersehentlichGesetzteBewertungAufNichtBewertetZurueck()
|
||||
{
|
||||
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.SetRatingByNumber(4); // aus Versehen eine Note für Qualität vergeben
|
||||
|
||||
vm.ClearRating();
|
||||
|
||||
Assert.Null(vm.AspectRows[0].Value);
|
||||
Assert.Null(rows[0].GetRating("quality"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VorherigerAspekt_SpringtRueckwaertsMitUmlauf()
|
||||
{
|
||||
|
||||
@@ -483,6 +483,35 @@ public sealed class SeatingPlanViewModelTests
|
||||
Assert.True(entry.HomeworkMissing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SitzplatzBewertung_SchlaegtQuantitaetAusStrichlisteVor()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var studentId = Guid.NewGuid();
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var session = new ParticipationSession { GroupId = groupId, Date = today };
|
||||
var sessions = new FakeSessions([session]);
|
||||
var entries = new FakeEntries();
|
||||
entries.Add(new ParticipationEntry { SessionId = session.Id, StudentId = studentId, RaisedHandCount = 3 });
|
||||
|
||||
var vm = new SeatAssessmentViewModel(sessions, entries, new FakeAspects(),
|
||||
groupId, studentId, "Beispiel, Anna", canEdit: true);
|
||||
|
||||
var quantityRow = vm.AspectRows.Single(r => r.Key == "quantity");
|
||||
Assert.True(quantityRow.HasSuggestion);
|
||||
Assert.Equal(0, quantityRow.SuggestedValue); // 3 gemeldet -> "∼" laut ParticipationCountSuggestion
|
||||
Assert.Contains("3× gemeldet", quantityRow.SuggestionLabel);
|
||||
|
||||
var qualityRow = vm.AspectRows.Single(r => r.Key == "quality");
|
||||
Assert.False(qualityRow.HasSuggestion);
|
||||
|
||||
quantityRow.ApplySuggestionCommand.Execute(null);
|
||||
|
||||
Assert.Equal(0, quantityRow.Value);
|
||||
var updatedEntry = entries.GetBySessionAndStudent(session.Id, studentId)!;
|
||||
Assert.Equal(0, updatedEntry.Ratings.Single(r => r.Key == "quantity").Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SitzplatzBewertung_ArchiviertOhneSitzung_LegtKeineNeueSitzungAn()
|
||||
{
|
||||
@@ -517,6 +546,60 @@ public sealed class SeatingPlanViewModelTests
|
||||
Assert.Null(vm.SelectedSession);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TallyRaisedHand_ErhoehtNurGemeldetZaehler()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "A" };
|
||||
var plan = new SeatingPlan
|
||||
{
|
||||
GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1,
|
||||
Assignments = [new SeatAssignment { StudentId = student.Id }],
|
||||
};
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||||
new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]),
|
||||
sessions, entries, new FakeAspects());
|
||||
vm.Initialize(groupId, isReadOnly: false);
|
||||
var seat = vm.Seats.Single();
|
||||
|
||||
seat.TallyRaisedHandCommand.Execute(null);
|
||||
seat.TallyRaisedHandCommand.Execute(null);
|
||||
|
||||
Assert.Equal(2, seat.RaisedHandCount);
|
||||
Assert.Equal(0, seat.CalledOnCount);
|
||||
var session = Assert.Single(sessions.GetByGroup(groupId));
|
||||
var entry = entries.GetBySessionAndStudent(session.Id, student.Id)!;
|
||||
Assert.Equal(2, entry.RaisedHandCount);
|
||||
Assert.Equal(0, entry.CalledOnCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TallyCalledOn_ErhoehtBeideZaehler()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var student = new Student { FirstName = "Anna", LastName = "A" };
|
||||
var plan = new SeatingPlan
|
||||
{
|
||||
GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1,
|
||||
Assignments = [new SeatAssignment { StudentId = student.Id }],
|
||||
};
|
||||
var sessions = new FakeSessions([]);
|
||||
var entries = new FakeEntries();
|
||||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]),
|
||||
new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]),
|
||||
sessions, entries, new FakeAspects());
|
||||
vm.Initialize(groupId, isReadOnly: false);
|
||||
var seat = vm.Seats.Single();
|
||||
|
||||
seat.TallyRaisedHandCommand.Execute(null); // erste Meldung, nicht drangekommen
|
||||
seat.TallyCalledOnCommand.Execute(null); // zweite Meldung, drangekommen
|
||||
|
||||
Assert.Equal(2, seat.RaisedHandCount);
|
||||
Assert.Equal(1, seat.CalledOnCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssessStudent_LegtErstBeiTatsaechlicherBewertungEineSitzungAn()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user