using LehrerApp.Core.Models; using LehrerApp.Desktop.ViewModels.Students; using Xunit; namespace LehrerApp.Desktop.Tests; public sealed class DocumentationDialogViewModelTests { [Fact] public void Save_ContentIstNull_WirftNichtUndSpeichertLeerenText() { // Regression: Avalonias TextBox.Text kann beim vollständigen Leeren des Felds über das // Zwei-Wege-Binding null statt "" liefern (beobachtet bei einem Fehlzeit-Eintrag ohne // Beschreibung) — Content.Trim() ohne Null-Schutz warf eine NullReferenceException. var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()) { Title = "Fehlt am Montag", TypeName = "Fehlzeit", Content = null!, }; vm.SaveCommand.Execute(null); Assert.NotNull(vm.Result); Assert.Equal("", vm.Result!.Content); } [Fact] public void Save_ElternbriefFelderSindNull_WirftNichtUndSpeichertLeerenText() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()) { Title = "Brief", TypeName = "Elternbrief", LetterDraftContent = null!, LetterResponseNote = null!, }; vm.SaveCommand.Execute(null); Assert.NotNull(vm.Result); Assert.Equal("", vm.Result!.ParentLetterData!.DraftContent); Assert.Equal("", vm.Result.ParentLetterData.ResponseNote); } [Fact] public void AddTag_NewTagIstNull_WirftNichtUndFuegtNichtsHinzu() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()) { NewTag = null!, }; vm.AddTagCommand.Execute(null); Assert.Empty(vm.Tags); } [Fact] public void Save_UebernimmtHinzugefuegteLabelsUndVerhindertDuplikate() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()) { Title = "Vorfall im Unterricht", TypeName = "Vorkommnis", }; vm.NewTag = "Kritisch"; vm.AddTagCommand.Execute(null); vm.NewTag = "Kritisch"; // Duplikat, soll ignoriert werden vm.AddTagCommand.Execute(null); vm.NewTag = "Mit JGL abklären"; vm.AddTagCommand.Execute(null); vm.SaveCommand.Execute(null); Assert.Equal(["Kritisch", "Mit JGL abklären"], vm.Result!.Tags); } [Fact] public void RemoveTag_EntferntDasLabelWiederAusDerListe() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()); vm.NewTag = "Beobachten"; vm.AddTagCommand.Execute(null); vm.RemoveTagCommand.Execute("Beobachten"); Assert.Empty(vm.Tags); } [Fact] public void Save_Elternanruf_UebernimmtGeplanteGespraechspunkte() { var studentId = Guid.NewGuid(); var vm = new DocumentationDialogViewModel(studentId, null, new FakeAttachmentStorage()) { Title = "Anruf wegen Hausaufgaben", TypeName = "Elternanruf", }; vm.NewCallPoint = "Hausaufgabensituation ansprechen"; vm.AddCallPointCommand.Execute(null); vm.NewCallPoint = "Nächste Schritte vereinbaren"; vm.AddCallPointCommand.Execute(null); vm.SaveCommand.Execute(null); Assert.NotNull(vm.Result); Assert.Equal(DocumentationType.ParentCall, vm.Result!.Type); Assert.Equal(2, vm.Result.ParentCallData!.Points.Count); Assert.All(vm.Result.ParentCallData.Points, p => Assert.False(p.IsDone)); Assert.False(vm.Result.ParentCallData.IsConducted); } [Fact] public void Save_ElternanrufBearbeiten_BehaeltAbgehakteStatusFuerUnveraenderteePunkte() { var studentId = Guid.NewGuid(); var existing = new Documentation { StudentId = studentId, Type = DocumentationType.ParentCall, Title = "Anruf", ParentCallData = new ParentCallData { Points = [new ParentCallPoint { Text = "Punkt A", IsDone = true }], IsConducted = true, ConductedDate = new DateOnly(2026, 1, 10), Impressions = "Gut verlaufen", }, }; var vm = new DocumentationDialogViewModel(studentId, existing, new FakeAttachmentStorage()); vm.NewCallPoint = "Punkt B"; vm.AddCallPointCommand.Execute(null); vm.SaveCommand.Execute(null); var points = vm.Result!.ParentCallData!.Points; Assert.True(points.Single(p => p.Text == "Punkt A").IsDone); Assert.False(points.Single(p => p.Text == "Punkt B").IsDone); Assert.True(vm.Result.ParentCallData.IsConducted); Assert.Equal("Gut verlaufen", vm.Result.ParentCallData.Impressions); } [Fact] public void Save_SitzplanEntwurf_WirdAbgeschlossenUndBehaeltStundenbezug() { var sessionId = Guid.NewGuid(); var existing = new Documentation { StudentId = Guid.NewGuid(), Type = DocumentationType.Incident, Date = DateOnly.FromDateTime(DateTime.Today), Title = "Streit/Konflikt", IsDraft = true, ParticipationSessionId = sessionId, Tags = ["Streit/Konflikt"], }; var vm = new DocumentationDialogViewModel(existing.StudentId, existing, new FakeAttachmentStorage()); vm.SaveCommand.Execute(null); Assert.False(vm.Result!.IsDraft); Assert.Equal(sessionId, vm.Result.ParticipationSessionId); } [Fact] public void Save_Elternbrief_UebernimmtVersandUndRueckmeldung() { var studentId = Guid.NewGuid(); var vm = new DocumentationDialogViewModel(studentId, null, new FakeAttachmentStorage()) { Title = "Brief wegen Verhalten", TypeName = "Elternbrief", LetterDraftContent = "Sehr geehrte...", LetterSentDateText = "05.09.2025", LetterResponseReceived = true, LetterResponseDateText = "12.09.2025", LetterResponseNote = "Termin vereinbart", }; vm.SaveCommand.Execute(null); var letter = vm.Result!.ParentLetterData!; Assert.Equal(new DateOnly(2025, 9, 5), letter.SentDate); Assert.True(letter.ResponseReceived); Assert.Equal(new DateOnly(2025, 9, 12), letter.ResponseDate); Assert.Equal("Termin vereinbart", letter.ResponseNote); } [Fact] public void Save_UngueltigesDatumBeimElternbrief_SetztFehlerUndSpeichertNicht() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()) { Title = "Brief", TypeName = "Elternbrief", LetterSentDateText = "nicht-valide", }; vm.SaveCommand.Execute(null); Assert.Null(vm.Result); Assert.NotEqual("", vm.LetterSentDateError); } [Fact] public void AddAttachment_GroessereDateiAlsDasLimit_SetztFehlerUndFuegtNichtHinzu() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()); using var huge = new MemoryStream(new byte[LehrerApp.Core.Interfaces.IAttachmentStorage.MaxSizeBytes + 1]); vm.AddAttachment("gross.bin", huge); Assert.Empty(vm.Attachments); Assert.NotEqual("", vm.AttachmentError); } [Fact] public void OhneStudentOptions_CanPickStudentIstFalse() { var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, new FakeAttachmentStorage()); Assert.False(vm.CanPickStudent); } [Fact] public void MitStudentOptions_OhneAuswahl_SaveSetztFehlerUndSpeichertNicht() { var anna = new StudentOption(Guid.NewGuid(), "Anna Beispiel"); var vm = new DocumentationDialogViewModel(Guid.Empty, null, new FakeAttachmentStorage(), [anna]) { Title = "Vorfall", TypeName = "Vorkommnis", }; vm.SaveCommand.Execute(null); Assert.Null(vm.Result); Assert.NotEqual("", vm.StudentError); } [Fact] public void MitStudentOptions_AuswahlGetroffen_SaveUebernimmtSchuelerUndGruppe() { var groupId = Guid.NewGuid(); var anna = new StudentOption(Guid.NewGuid(), "Anna Beispiel"); var ben = new StudentOption(Guid.NewGuid(), "Ben Muster"); var vm = new DocumentationDialogViewModel(Guid.Empty, null, new FakeAttachmentStorage(), [anna, ben], groupId) { Title = "Vorfall", TypeName = "Vorkommnis", SelectedStudent = ben, }; vm.SaveCommand.Execute(null); Assert.NotNull(vm.Result); Assert.Equal(ben.Id, vm.Result!.StudentId); Assert.Equal(groupId, vm.Result.GroupId); } [Fact] public void MitStudentOptions_Bearbeiten_SchuelerIstVorausgewaehlt() { var anna = new StudentOption(Guid.NewGuid(), "Anna Beispiel"); var ben = new StudentOption(Guid.NewGuid(), "Ben Muster"); var existing = new Documentation { StudentId = ben.Id, Title = "Alt" }; var vm = new DocumentationDialogViewModel(ben.Id, existing, new FakeAttachmentStorage(), [anna, ben]); Assert.Equal(ben, vm.SelectedStudent); } [Fact] public void DiscardUnsavedAttachments_EntferntNurNieGespeicherteAnhaenge() { var storage = new FakeAttachmentStorage(); var vm = new DocumentationDialogViewModel(Guid.NewGuid(), null, storage) { Title = "Brief", TypeName = "Elternbrief", }; using var content = new MemoryStream([1, 2, 3]); vm.AddAttachment("brief.pdf", content); var storageId = vm.Attachments.Single().StorageId; vm.DiscardUnsavedAttachments(); Assert.Null(storage.OpenRead(storageId)); } }