feat: Formulare-Menü mit Elternbrief-Einstieg + Arbeitsblatt-Personalisierung (Nutzer-Feedback)
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Neue Menü-Kategorie "Formulare" in der Menüleiste: "Elternbrief erzeugen..." öffnet jetzt einen Schüler-Picker statt nur aus der Schülerdetailansicht erreichbar zu sein, "Arbeitsblatt personalisieren..." ist aktiv, sobald eine einzelne Lerngruppe geöffnet ist, und erzeugt aus einer in TemplateDesigner gebauten .lavorlage-Vorlage ein PDF je aktivem Gruppenmitglied - über eine von den Elternbrief-Vorlagen getrennte WorksheetTemplateStore-Bibliothek. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using LehrerApp.Templating;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class PersonalizeWorksheetDialogViewModelTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), $"lehrerapp-worksheet-vm-tests-{Guid.NewGuid():N}");
|
||||
public PersonalizeWorksheetDialogViewModelTests() => Directory.CreateDirectory(_directory);
|
||||
|
||||
[Fact]
|
||||
public void Generate_ErzeugtEinePdfJeAusgewaehltemSchueler()
|
||||
{
|
||||
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
|
||||
var anna = new Student { FirstName = "Anna", LastName = "Adler" };
|
||||
var ben = new Student { FirstName = "Ben", LastName = "Bauer" };
|
||||
var vm = Build(group, [anna, ben], StoreWithTemplate());
|
||||
var output = Path.Combine(_directory, "output");
|
||||
|
||||
vm.Generate(output);
|
||||
|
||||
Assert.Equal(2, vm.Results.Count);
|
||||
Assert.All(vm.Results, r => Assert.True(r.Success));
|
||||
Assert.Equal(2, Directory.GetFiles(output, "*.pdf").Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_AbgewaehlterSchueler_WirdUebersprungen()
|
||||
{
|
||||
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
|
||||
var anna = new Student { FirstName = "Anna", LastName = "Adler" };
|
||||
var ben = new Student { FirstName = "Ben", LastName = "Bauer" };
|
||||
var vm = Build(group, [anna, ben], StoreWithTemplate());
|
||||
vm.Students.Single(s => s.Model.Id == ben.Id).IsIncluded = false;
|
||||
var output = Path.Combine(_directory, "output");
|
||||
|
||||
vm.Generate(output);
|
||||
|
||||
Assert.Single(vm.Results);
|
||||
Assert.Equal(anna.FullName, vm.Results[0].StudentName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OhneVorlage_GenerateTutNichts()
|
||||
{
|
||||
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
|
||||
var anna = new Student { FirstName = "Anna", LastName = "Adler" };
|
||||
var vm = Build(group, [anna], new WorksheetTemplateStore(new TemplateStore(Path.Combine(_directory, "empty-store"))));
|
||||
var output = Path.Combine(_directory, "output");
|
||||
|
||||
vm.Generate(output);
|
||||
|
||||
Assert.Empty(vm.Results);
|
||||
}
|
||||
|
||||
private static PersonalizeWorksheetDialogViewModel Build(LearningGroup group, List<Student> students, WorksheetTemplateStore store) =>
|
||||
new(group, [.. students.Select(s => s.Id)], new FakeStudents(students), store, new QuestTemplateRenderer());
|
||||
|
||||
private WorksheetTemplateStore StoreWithTemplate()
|
||||
{
|
||||
var source = Path.Combine(_directory, $"{Guid.NewGuid():N}.lavorlage");
|
||||
var manifest = new TemplateManifest
|
||||
{
|
||||
Id = $"arbeitsblatt-{Guid.NewGuid():N}", Name = "Arbeitsblatt",
|
||||
Placeholders = [new PlaceholderDefinition("Student.FirstName", PlaceholderType.Text)],
|
||||
};
|
||||
TemplatePackage.Create(source, manifest, "PAGE 210 297 mm\nTEXT 20 20 $Student.FirstName", new Dictionary<string, byte[]>());
|
||||
var store = new WorksheetTemplateStore(new TemplateStore(Path.Combine(_directory, $"store-{Guid.NewGuid():N}")));
|
||||
store.Store.Import(source);
|
||||
return store;
|
||||
}
|
||||
|
||||
public void Dispose() { if (Directory.Exists(_directory)) Directory.Delete(_directory, true); }
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public sealed class SettingsViewModelTests
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
holidays ?? new FakeSchoolHolidays(), new SchoolCalendarSettingsService(tempPath),
|
||||
new PeriodScheduleService(tempPath), supervisionDuties ?? new FakeSupervisionDuties(),
|
||||
new TemplateStore(tempPath), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
new TemplateStore(tempPath), new WorksheetTemplateStore(new TemplateStore(tempPath, subfolder: "worksheet-template-packages")), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
TestSupport.BuildMcpSettingsService(),
|
||||
TestSupport.BuildMcpClientRegistrationService(),
|
||||
TestSupport.BuildWebUntisSettingsService(),
|
||||
@@ -342,7 +342,7 @@ public sealed class SettingsViewModelTests
|
||||
new LiteDbContext(new MemoryStream()), new PrivacySettingsService(tempPath),
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
new FakeSchoolHolidays(), calendarSettings, new PeriodScheduleService(tempPath),
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), new WorksheetTemplateStore(new TemplateStore(tempPath, subfolder: "worksheet-template-packages")), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
TestSupport.BuildMcpSettingsService(),
|
||||
TestSupport.BuildMcpClientRegistrationService(),
|
||||
TestSupport.BuildWebUntisSettingsService(),
|
||||
@@ -372,7 +372,7 @@ public sealed class SettingsViewModelTests
|
||||
new LiteDbContext(new MemoryStream()), new PrivacySettingsService(tempPath),
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
new FakeSchoolHolidays(), new SchoolCalendarSettingsService(tempPath), periodSchedule,
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), new WorksheetTemplateStore(new TemplateStore(tempPath, subfolder: "worksheet-template-packages")), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
TestSupport.BuildMcpSettingsService(),
|
||||
TestSupport.BuildMcpClientRegistrationService(),
|
||||
TestSupport.BuildWebUntisSettingsService(),
|
||||
@@ -406,7 +406,7 @@ public sealed class SettingsViewModelTests
|
||||
new LiteDbContext(new MemoryStream()), new PrivacySettingsService(tempPath),
|
||||
new FakeDocumentation(), new FakeStudents([]), new FakeShorthandCodes([]),
|
||||
new FakeSchoolHolidays(), new SchoolCalendarSettingsService(tempPath), periodSchedule,
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
new FakeSupervisionDuties(), new TemplateStore(tempPath), new WorksheetTemplateStore(new TemplateStore(tempPath, subfolder: "worksheet-template-packages")), TestSupport.BuildAiSettingsService(), TestSupport.BuildAiPlanningService(),
|
||||
TestSupport.BuildMcpSettingsService(),
|
||||
TestSupport.BuildMcpClientRegistrationService(),
|
||||
TestSupport.BuildWebUntisSettingsService(),
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.Students;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class StudentPickerDialogViewModelTests
|
||||
{
|
||||
private static (StudentPickerDialogViewModel Vm, Student Anna, Student Ben) Build()
|
||||
{
|
||||
var anna = new Student { FirstName = "Anna", LastName = "Adler" };
|
||||
var ben = new Student { FirstName = "Ben", LastName = "Bauer" };
|
||||
var vm = new StudentPickerDialogViewModel(new FakeStudents([anna, ben]));
|
||||
return (vm, anna, ben);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SearchText_FiltertNachTeilstring()
|
||||
{
|
||||
var (vm, anna, _) = Build();
|
||||
|
||||
vm.SearchText = "ann";
|
||||
|
||||
Assert.Single(vm.Students);
|
||||
Assert.Equal(anna.FullName, vm.Students[0].FullName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_OhneAuswahl_SetztValidationMessage()
|
||||
{
|
||||
var (vm, _, _) = Build();
|
||||
|
||||
vm.SelectCommand.Execute(null);
|
||||
|
||||
Assert.Null(vm.Result);
|
||||
Assert.NotEqual("", vm.ValidationMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_MitAuswahl_LiefertVollenSchueler()
|
||||
{
|
||||
var (vm, anna, _) = Build();
|
||||
vm.SelectedStudent = vm.Students.Single(s => s.Id == anna.Id);
|
||||
|
||||
vm.SelectCommand.Execute(null);
|
||||
|
||||
Assert.Equal(anna.Id, vm.Result?.Id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user