231 lines
11 KiB
C#
231 lines
11 KiB
C#
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 async Task 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);
|
|
await vm.SetAttendanceCalendarOptionsAsync(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 async Task AttendanceKalenderKonfigurieren_LoestGezieltenDatenAbrufFuerDenZeitraumAus()
|
|
{
|
|
var store = StoreWithTemplate(new PlaceholderDefinition(
|
|
StudentAttendanceCalendarDrawingBuilder.PlaceholderName, PlaceholderType.Drawing, true));
|
|
AttendanceCalendarOptions? requested = null;
|
|
var vm = new CreateLetterDialogViewModel(StudentWithContact("Sehr geehrte Frau Muster,"), store,
|
|
new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]),
|
|
_ => new DrawingValue([], 0), attendanceDataRefresher: (options, _) =>
|
|
{ requested = options; return Task.CompletedTask; });
|
|
|
|
await vm.SetAttendanceCalendarOptionsAsync(new AttendanceCalendarOptions(
|
|
new DateOnly(2026, 9, 1), 2, AttendanceCalendarSize.Medium));
|
|
|
|
Assert.NotNull(requested);
|
|
Assert.Equal(new DateOnly(2026, 9, 1), requested!.NormalizedStartMonth);
|
|
Assert.Equal(2, requested.NormalizedMonthCount);
|
|
Assert.False(vm.IsRefreshingAttendanceData);
|
|
Assert.Equal("", vm.AttendanceRefreshError);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AttendanceKalenderKonfigurieren_ZeigtFehlerBeiFehlgeschlagenemAbrufAnStattZuBlockieren()
|
|
{
|
|
var store = StoreWithTemplate(new PlaceholderDefinition(
|
|
StudentAttendanceCalendarDrawingBuilder.PlaceholderName, PlaceholderType.Drawing, true));
|
|
var vm = new CreateLetterDialogViewModel(StudentWithContact("Sehr geehrte Frau Muster,"), store,
|
|
new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]),
|
|
_ => new DrawingValue([], 0),
|
|
attendanceDataRefresher: (_, _) => throw new WebUntisIntegrationException("Keine Verbindung."));
|
|
|
|
await vm.SetAttendanceCalendarOptionsAsync(new AttendanceCalendarOptions(
|
|
new DateOnly(2026, 9, 1), 1, AttendanceCalendarSize.Medium));
|
|
|
|
Assert.True(vm.AttendanceCalendarConfigured);
|
|
Assert.Contains("Keine Verbindung.", vm.AttendanceRefreshError);
|
|
Assert.False(vm.IsRefreshingAttendanceData);
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
[Fact]
|
|
public void EigenerPlatzhalter_KannImDialogEingegebenWerden()
|
|
{
|
|
var store = StoreWithTemplate(new PlaceholderDefinition("Anrede", PlaceholderType.Text, true),
|
|
new PlaceholderDefinition("Datum", PlaceholderType.Date, true),
|
|
new PlaceholderDefinition("Brieftext", PlaceholderType.Multiline, true),
|
|
new PlaceholderDefinition("Betreff", PlaceholderType.Text, true));
|
|
var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), store);
|
|
vm.LetterText = "Dies ist der Inhalt.";
|
|
|
|
var betreff = Assert.Single(vm.CustomPlaceholders);
|
|
Assert.Equal("Betreff", betreff.Name);
|
|
Assert.False(vm.CanGenerate);
|
|
Assert.Contains(vm.Issues, i => i.Message.Contains("Betreff", StringComparison.OrdinalIgnoreCase));
|
|
|
|
betreff.TextValue = "Wichtiger Termin";
|
|
var output = Path.Combine(_directory, "MitBetreff.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 StudentName_WirdAutomatischMitVollemNamenBefuellt()
|
|
{
|
|
var store = StoreWithTemplate(new PlaceholderDefinition("Anrede", PlaceholderType.Text, true),
|
|
new PlaceholderDefinition("Datum", PlaceholderType.Date, true),
|
|
new PlaceholderDefinition("Brieftext", PlaceholderType.Multiline, true),
|
|
new PlaceholderDefinition("Student.Name", PlaceholderType.Text, true));
|
|
var vm = Build(StudentWithContact("Sehr geehrte Frau Muster,"), store);
|
|
vm.LetterText = "Dies ist der Inhalt.";
|
|
|
|
Assert.Empty(vm.CustomPlaceholders);
|
|
Assert.True(vm.CanGenerate);
|
|
var output = Path.Combine(_directory, "StudentName.pdf");
|
|
Assert.True(vm.Generate(output));
|
|
}
|
|
|
|
[Fact]
|
|
public void Anrede_WirdAusKontaktVorbelegtUndBleibtEditierbar()
|
|
{
|
|
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(null), store);
|
|
vm.LetterText = "Dies ist der Inhalt.";
|
|
|
|
Assert.Equal("", vm.Anrede);
|
|
Assert.False(vm.CanGenerate);
|
|
Assert.Contains(vm.Issues, i => i.Message.Contains("Anrede", StringComparison.OrdinalIgnoreCase));
|
|
|
|
vm.Anrede = "Sehr geehrte Familie Beispiel,";
|
|
var output = Path.Combine(_directory, "AnredeManuell.pdf");
|
|
|
|
Assert.True(vm.CanGenerate);
|
|
Assert.True(vm.Generate(output));
|
|
}
|
|
|
|
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<string> { "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<string, byte[]>());
|
|
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); }
|
|
}
|