using Bunit; using Duempelkas.App.Components.Dialogs; using Duempelkas.App.Services; using Duempelkas.App.Services.Models; using Duempelkas.Domain.Enums; using Duempelkas.Infrastructure.Services; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Duempelkas.Tests; public class BookingFormTests { [Fact] public async Task PendingDate_IsProtectedOnCancel_AndValidatedBeforeSubmit() { using var f = new LedgerUiFixture(); var dialog = f.Context.Render(p => p.Add(x => x.AccountId, 1).Add(x => x.ContextYear, DateTime.Today.Year)); dialog.Find("#entry-date").Input($"{DateTime.Today.Year + 1}-01-02"); Assert.Empty(dialog.FindAll(".booking-year-warning")); var modal = dialog.FindComponent(); await modal.InvokeAsync(() => modal.Instance.RequestCloseAsync()); Assert.NotEmpty(dialog.FindAll("[role=alertdialog]")); dialog.Find("[data-discard-continue]").Click(); dialog.Find("#entry-title").Input("Pending date"); dialog.Find("#entry-amount").Input("12,50"); // A keyboard submit can happen without the date field losing focus first. dialog.Find("form").Submit(); Assert.NotEmpty(dialog.FindAll(".booking-year-warning")); Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled")); Assert.Empty(await f.Service().GetEntriesAsync(1, (int?)null)); } [Fact] public void Form_ExplainsMissingFields_AndDoesNotRoundInvalidInput() { using var context = new BunitContext(); context.JSInterop.Mode = JSRuntimeMode.Loose; context.Services.AddSingleton(); context.Services.AddSingleton(new EntryService(null!)); var dialog = context.Render(p => p.Add(x => x.AccountName, "Barkasse")); Assert.Contains("Barkasse", dialog.Find("h5").TextContent); Assert.Contains("Bitte beschreiben", dialog.Markup); Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled")); dialog.Find("#entry-title").Input("Porto"); dialog.Find("#entry-amount").Input("12,501"); Assert.Equal("", dialog.Find("#entry-amount").GetAttribute("value")); Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled")); dialog.Find("#entry-amount").Input("-12,50"); Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled")); dialog.Find("#entry-amount").Input("12,50"); Assert.False(dialog.Find("button.btn-success").HasAttribute("disabled")); dialog.Find("#entry-date").Input(""); dialog.Find("#entry-date").Blur(); Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled")); Assert.Contains("Bitte ein gültiges Datum", dialog.Markup); dialog.Find("#entry-date").Input("2026-01-02"); dialog.Find("#entry-date").Blur(); Assert.False(dialog.Find("button.btn-success").HasAttribute("disabled")); } [Fact] public void Edit_ShowsFixedTypeAndCorrectionPath_AndRerenderKeepsInput() { using var context = new BunitContext(); context.JSInterop.Mode = JSRuntimeMode.Loose; context.Services.AddSingleton(); context.Services.AddSingleton(new EntryService(null!)); var entry = new EntryDto(1, 1, "2026-001", EntryType.Expense, new(2026, 1, 1), "Porto", 4.20m, false, false, null, null, null); var dialog = context.Render(p => p.Add(x => x.EditEntry, entry)); Assert.Contains("Ausgabe", dialog.Markup); Assert.Contains("mit der richtigen Art neu erfassen", dialog.Markup); dialog.Find("#entry-title").Input("Korrigierter Text"); dialog.Render(p => p.Add(x => x.EditEntry, entry)); Assert.Equal("Korrigierter Text", dialog.Find("#entry-title").GetAttribute("value")); } }