using LehrerApp.Core.Models; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.Students; using LehrerApp.Templating; using Xunit; namespace LehrerApp.Desktop.Tests; public sealed class CreateLetterDialogViewModelTests : IDisposable { private readonly string _directory = Path.Combine(Path.GetTempPath(), $"lehrerapp-letter-vm-tests-{Guid.NewGuid():N}"); public CreateLetterDialogViewModelTests() => Directory.CreateDirectory(_directory); [Fact] public void OhneVorlage_KannKeinenBriefErzeugen() { var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), new TemplateStore(_directory)); Assert.False(vm.CanGenerate); Assert.Contains(vm.Issues, i => i.Message.Contains("Vorlage", StringComparison.OrdinalIgnoreCase)); } [Fact] public void VerwendeteBriefanredeFehlt_BlockiertErzeugung() { var vm = Build(StudentWithContact(null), StoreWithTemplate( new PlaceholderDefinition("Anrede", PlaceholderType.Text, true))); Assert.False(vm.CanGenerate); Assert.Contains(vm.Issues, i => i.Message.Contains("Anrede", StringComparison.OrdinalIgnoreCase)); } [Fact] public void VollstaendigeDaten_ErzeugenFlachesPdf() { var store = StoreWithTemplate(new PlaceholderDefinition("Anrede", PlaceholderType.Text, true), new PlaceholderDefinition("Datum", PlaceholderType.Date, true), new PlaceholderDefinition("Brieftext", PlaceholderType.Multiline, true)); var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), store); vm.LetterText = "Dies ist der Inhalt."; var output = Path.Combine(_directory, "Brief.pdf"); Assert.True(vm.Generate(output)); Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4)); } [Fact] public void KonstanterPflichttext_BenoetigtKeineExterneEingabe() { var store = StoreWithTemplate(new PlaceholderDefinition("Brieftext", PlaceholderType.Multiline, Required: true, IsConstant: true, ConstantValue: "Fest im Vorlagenpaket")); var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), store); var output = Path.Combine(_directory, "Konstant.pdf"); Assert.True(vm.CanGenerate); Assert.True(vm.Generate(output)); Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4)); } [Fact] public void AdvancedContent_Anwesenheitskalender_WirdAlsDrawingGerendert() { var store = StoreWithTemplate(new PlaceholderDefinition( StudentAttendanceCalendarDrawingBuilder.PlaceholderName, PlaceholderType.Drawing, true)); var drawing = StudentAttendanceCalendarDrawingBuilder.Build("Lena Beispiel", new DateOnly(2026, 9, 1), [], []); var vm = new CreateLetterDialogViewModel(StudentWithContact("Sehr geehrte Frau Muster,"), store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]), _ => drawing); var output = Path.Combine(_directory, "Kalender.pdf"); Assert.True(vm.UsesAttendanceCalendar); vm.SetAttendanceCalendarOptions(new AttendanceCalendarOptions( new DateOnly(2026, 8, 19), 3, AttendanceCalendarSize.Large)); Assert.True(vm.AttendanceCalendarConfigured); Assert.Contains("August 2026", vm.AttendanceCalendarSummary); Assert.Contains("3 Monate", vm.AttendanceCalendarSummary); Assert.Contains("Groß", vm.AttendanceCalendarSummary); Assert.True(vm.Generate(output)); Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4)); } [Fact] public void AdvancedContent_Fehlzeitenliste_AktiviertKonfigurationsschritt() { var store = StoreWithTemplate(new PlaceholderDefinition( StudentAbsenceDayListDrawingBuilder.PlaceholderName, PlaceholderType.Drawing, true)); var drawing = StudentAbsenceDayListDrawingBuilder.Build("Lena Beispiel", new AttendanceCalendarOptions(new DateOnly(2026, 9, 1), 1), []); var vm = new CreateLetterDialogViewModel(StudentWithContact("Sehr geehrte Frau Muster,"), store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]), attendanceCalendarFactory: null, absenceDayListFactory: _ => drawing); Assert.False(vm.UsesAttendanceCalendar); Assert.True(vm.UsesAbsenceDayList); Assert.True(vm.UsesAttendanceAdvancedContent); Assert.True(vm.CanGenerate); } private CreateLetterDialogViewModel Build(Student student, TemplateStore store) => new(student, store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([])); private TemplateStore StoreWithTemplate(params PlaceholderDefinition[] definitions) { var source = Path.Combine(_directory, $"{Guid.NewGuid():N}.lavorlage"); var manifest = new TemplateManifest { Id = $"brief-{Guid.NewGuid():N}", Name = "Elternbrief", Placeholders = [.. definitions] }; var lines = new List { "PAGE 210 297 mm" }; var y = 20; foreach (var definition in definitions) { var element = definition.Type switch { PlaceholderType.Multiline => "TEXTBOX", PlaceholderType.Drawing => "DRAWBOX", _ => "TEXT", }; lines.Add(element switch { "TEXTBOX" => $"TEXTBOX 20 {y} 170 80 ${definition.Name}", "DRAWBOX" => $"DRAWBOX 20 {y} 170 80 ${definition.Name}", _ => $"TEXT 20 {y} ${definition.Name}", }); y += 20; } TemplatePackage.Create(source, manifest, string.Join('\n', lines), new Dictionary()); var store = new TemplateStore(Path.Combine(_directory, $"store-{Guid.NewGuid():N}")); store.Import(source); return store; } private static Student StudentWithContact(string? salutation) => new() { FirstName = "Lena", LastName = "Beispiel", Contacts = [new Contact { Name = "Frau Muster", Relation = "Mutter", LetterSalutation = salutation, Street = "Hauptstraße 1", PostalCode = "12345", City = "Musterstadt" }], }; public void Dispose() { if (Directory.Exists(_directory)) Directory.Delete(_directory, true); } }