fix(ui): refine amount entry, date warnings and booking actions

This commit is contained in:
2026-11-02 20:31:09 +01:00
parent 71eed68e21
commit aa392815e5
17 changed files with 140 additions and 39 deletions
@@ -0,0 +1,26 @@
using Bunit;
using Duempelkas.App.Components;
using Xunit;
namespace Duempelkas.Tests;
public class BookingDateInputTests
{
[Fact]
public void DateIsValidatedOnLeavingField_WithoutInterruptingNativeCalendar()
{
using var context = new BunitContext();
var value = "2025-12-31";
var field = context.Render<BookingDateInput>(p => p
.Add(x => x.Value, value).Add(x => x.ValueChanged, next => value = next));
field.Find("input").Input("2026-01-01");
Assert.Equal("2025-12-31", value);
field.Find("input").Blur();
Assert.Equal("2026-01-01", value);
field.Render(p => p.Add(x => x.Value, value));
// The browser owns the edited value; avoid resetting its calendar by writing it back.
Assert.Equal("2025-12-31", field.Find("input").GetAttribute("value"));
field.Render(p => p.Add(x => x.Value, "2027-02-03"));
Assert.Equal("2027-02-03", field.Find("input").GetAttribute("value"));
}
}
@@ -11,6 +11,26 @@ namespace Duempelkas.Tests;
public class BookingFormTests
{
[Fact]
public async Task PendingDate_IsProtectedOnCancel_AndValidatedBeforeSubmit()
{
using var f = new LedgerUiFixture();
var dialog = f.Context.Render<AddEntryDialog>(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<ModalDialog>();
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<IEntryService>().GetEntriesAsync(1, (int?)null));
}
[Fact]
public void Form_ExplainsMissingFields_AndDoesNotRoundInvalidInput()
{
@@ -31,9 +51,11 @@ public class BookingFormTests
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"));
}
@@ -15,7 +15,7 @@ public class EntryLifecycleUiTests
await entries.CreateTransferAsync(1, 2, DateTime.Today, "Testtransfer", 20m);
var page = f.Context.Render<Routes>();
page.WaitForElement(".account-card").Click();
page.WaitForElement(".entry-action-menu");
page.WaitForElement(".entry-action-buttons");
page.Find("button.btn-outline-danger").Click();
page.WaitForAssertion(() => Assert.Contains("beiden Konten", page.Markup));
Assert.Contains("Barkasse", page.Find("dialog").TextContent);
@@ -18,14 +18,17 @@ public class EntryPresentationTests
Assert.DoesNotContain("emptyEntriesMessage", page.Markup);
}
[Fact]
public void Actions_HaveVisibleTextAndBookingSpecificNames()
public void Actions_AreDirectIconButtonsWithTitlesAndBookingSpecificNames()
{
using var context = new BunitContext();
var entry = new EntryDto(1, 1, "2026-001", EntryType.Expense, DateTime.Today, "Porto", 4.20m, false, false, null, null, null);
var edited = 0;
var table = context.Render<EntryTable>(p => p.Add(x => x.Entries, [entry]).Add(x => x.OnEditEntry, id => edited = id));
Assert.Contains("2026-001 Porto", table.Find("summary").GetAttribute("aria-label"));
var edit = table.FindAll("button").Single(b => b.TextContent.Contains("Bearbeiten"));
Assert.Empty(table.FindAll("summary"));
var edit = table.Find("button[title='Buchung bearbeiten']");
Assert.Contains("2026-001 Porto", edit.GetAttribute("aria-label"));
Assert.NotNull(edit.QuerySelector(".bi-pencil[aria-hidden=true]"));
Assert.NotNull(table.Find("button.btn-outline-danger .bi-trash"));
Assert.Contains("Porto", edit.GetAttribute("aria-label"));
edit.Click();
Assert.Equal(1, edited);
+1 -1
View File
@@ -36,7 +36,7 @@ public class ToastTests
var control = context.Render<AmountInput>(p => p.Add(x => x.Value, amount).Add(x => x.ValueChanged, value => amount = value));
control.Find("input").Input("12,501");
Assert.Equal("12,50", amount);
Assert.Contains("nicht übernommen", control.Markup);
Assert.Empty(control.FindAll("[role=status]"));
control.Find("input").Input("12.25");
Assert.Equal("12.25", amount);
}
@@ -43,9 +43,11 @@ public class YearNavigationTests
page.FindAll("button").Single(b => b.TextContent.Contains("Einnahme oder Ausgabe erfassen")).Click();
var dialog = page.FindComponent<AddEntryDialog>();
dialog.Find("input[type=date]").Input("2026-01-02");
dialog.Find("input[type=date]").Blur();
dialog.Find("#entry-title").Input("Außerhalb");
dialog.Find("#entry-amount").Input("12.50");
Assert.Contains("außerhalb", dialog.Markup);
Assert.NotNull(dialog.Find(".booking-year-warning[role=status] .bi-exclamation-triangle-fill"));
Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled"));
dialog.Find("input[type=checkbox]").Change(true);
dialog.Find("form").Submit();