feat(ui): constrain decimal input and add stacked timed toasts with uniform actions
This commit is contained in:
@@ -15,6 +15,7 @@ public class BookingFormTests
|
||||
public void Form_ExplainsMissingFields_AndDoesNotRoundInvalidInput()
|
||||
{
|
||||
using var context = new BunitContext();
|
||||
context.Services.AddSingleton<ToastService>();
|
||||
context.Services.AddSingleton<IEntryService>(new EntryService(null!));
|
||||
var dialog = context.Render<AddEntryDialog>(p => p.Add(x => x.AccountName, "Barkasse"));
|
||||
Assert.Contains("Barkasse", dialog.Find("h5").TextContent);
|
||||
@@ -22,7 +23,7 @@ public class BookingFormTests
|
||||
Assert.True(dialog.Find("button.btn-success").HasAttribute("disabled"));
|
||||
dialog.Find("#entry-title").Input("Porto");
|
||||
dialog.Find("#entry-amount").Input("12,501");
|
||||
Assert.Equal("12,501", dialog.Find("#entry-amount").GetAttribute("value"));
|
||||
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"));
|
||||
@@ -39,6 +40,7 @@ public class BookingFormTests
|
||||
public void Edit_ShowsFixedTypeAndCorrectionPath_AndRerenderKeepsInput()
|
||||
{
|
||||
using var context = new BunitContext();
|
||||
context.Services.AddSingleton<ToastService>();
|
||||
context.Services.AddSingleton<IEntryService>(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<AddEntryDialog>(p => p.Add(x => x.EditEntry, entry));
|
||||
|
||||
@@ -37,7 +37,7 @@ public class BookingSaveTests
|
||||
Assert.Equal(1, fixture.Entries.WriteCalls);
|
||||
delayed.SetResult();
|
||||
await pending;
|
||||
page.WaitForAssertion(() => Assert.Contains("gespeichert", page.Find(".alert-success").TextContent));
|
||||
page.WaitForAssertion(() => Assert.Contains("gespeichert", page.Find(".toast-success").TextContent));
|
||||
Assert.Single(await fixture.Entries.GetEntriesAsync(1, (int?)null));
|
||||
Assert.Single(page.FindAll("tr.entry-recent"));
|
||||
if (transfer) Assert.Single(await fixture.Entries.GetEntriesAsync(2, (int?)null));
|
||||
@@ -62,7 +62,7 @@ public class BookingSaveTests
|
||||
fixture.Entries.BeforeWrite = () => Task.FromException(new IOException("Simulierter Speicherfehler"));
|
||||
await page.Find("form").TriggerEventAsync("onsubmit", EventArgs.Empty);
|
||||
Assert.Contains("Eingaben bleiben erhalten", page.Markup);
|
||||
Assert.Empty(page.FindAll(".alert-success"));
|
||||
Assert.Empty(page.FindAll(".toast-success"));
|
||||
Assert.Equal("Testbuchung", page.Find(transfer ? "#transfer-title" : "#entry-title").GetAttribute("value"));
|
||||
Assert.Equal("12,50", page.Find(transfer ? "#transfer-amount" : "#entry-amount").GetAttribute("value"));
|
||||
Assert.Empty(await fixture.Entries.GetEntriesAsync(1, (int?)null));
|
||||
@@ -86,7 +86,7 @@ public class BookingSaveTests
|
||||
dialog.Find("#entry-title").Input("Testbuchung");
|
||||
dialog.Find("#entry-amount").Input("12,50");
|
||||
await dialog.Find("form").TriggerEventAsync("onsubmit", EventArgs.Empty);
|
||||
Assert.Contains("Die Buchung ist gespeichert", dialog.Markup);
|
||||
Assert.Contains(fixture.Context.Services.GetRequiredService<ToastService>().Messages, t => t.Message.Contains("Die Buchung ist gespeichert"));
|
||||
await dialog.Find("form").TriggerEventAsync("onsubmit", EventArgs.Empty);
|
||||
Assert.Equal(1, fixture.Entries.WriteCalls);
|
||||
Assert.Equal(2, notifications);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Bunit;
|
||||
using Duempelkas.App.Components;
|
||||
using Duempelkas.App.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Duempelkas.Tests;
|
||||
|
||||
public class ToastTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ToastsStack_ExpireIndependently_AndPauseDuringInteraction()
|
||||
{
|
||||
using var context = new BunitContext();
|
||||
context.Services.AddSingleton<ToastService>();
|
||||
var host = context.Render<ToastHost>();
|
||||
var service = context.Services.GetRequiredService<ToastService>();
|
||||
var first = service.Show("Erste Meldung", duration: TimeSpan.FromMilliseconds(100));
|
||||
service.Pause(first);
|
||||
service.Show("Zweite Meldung", ToastKind.Success, persistent: true);
|
||||
host.WaitForAssertion(() => Assert.Equal(2, host.FindAll(".app-toast").Count));
|
||||
await Task.Delay(200);
|
||||
Assert.Equal(2, service.Messages.Count);
|
||||
service.Resume(first);
|
||||
host.WaitForAssertion(() => Assert.Single(host.FindAll(".app-toast")));
|
||||
Assert.Contains("Zweite Meldung", host.Markup);
|
||||
host.Find(".toast-close").Click();
|
||||
Assert.Empty(service.Messages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AmountControl_RejectsWholePasteWithoutRounding()
|
||||
{
|
||||
using var context = new BunitContext();
|
||||
string amount = "12,50";
|
||||
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);
|
||||
control.Find("input").Input("12.25");
|
||||
Assert.Equal("12.25", amount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user